001/* 002 * Copyright 2016 RedRoma, Inc. 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.AromaException; 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, AromaException; 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 static Builder create() 089 { 090 return new Builder(); 091 } 092 093 private String hostname = ApplicationServiceConstants.PRODUCTION_ENDPOINT.getHostname(); 094 private int port = ApplicationServiceConstants.PRODUCTION_ENDPOINT.getPort(); 095 private String applicationToken = ""; 096 private ExecutorService async; 097 098 Builder() 099 { 100 101 } 102 103 /** 104 * Set the Token ID created from the Aroma App. 105 * 106 * @param applicationToken 107 * @return 108 * 109 * @throws IllegalArgumentException 110 */ 111 public Builder withApplicationToken(@Required String applicationToken) throws IllegalArgumentException 112 { 113 checkThat(applicationToken) 114 .is(nonEmptyString()); 115 116 this.applicationToken = applicationToken; 117 118 return this; 119 } 120 121 public Builder withEndpoint(@NonEmpty String hostname, int port) throws IllegalArgumentException 122 { 123 checkThat(hostname) 124 .usingMessage("hostname cannot be empty") 125 .is(nonEmptyString()); 126 127 checkThat(port) 128 .is(validPort()); 129 130 this.hostname = hostname; 131 this.port = port; 132 133 return this; 134 } 135 136 public Builder withAsyncExecutorService(@Required ExecutorService executor) throws IllegalArgumentException 137 { 138 checkThat(executor) 139 .is(notNull()); 140 141 this.async = executor; 142 143 return this; 144 } 145 146 public Aroma build() throws IllegalStateException 147 { 148 checkThat(hostname) 149 .throwing(IllegalStateException.class) 150 .usingMessage("missing hostname") 151 .is(nonEmptyString()); 152 153 checkThat(applicationToken) 154 .throwing(IllegalStateException.class) 155 .usingMessage("missing Application Token") 156 .is(nonEmptyString()); 157 158 checkThat(port) 159 .throwing(IllegalStateException.class) 160 .is(validPort()); 161 162 if (async == null) 163 { 164 async = Executors.newSingleThreadExecutor(); 165 } 166 167 Endpoint endpoint = createEndpoint(); 168 169 ApplicationToken token = new ApplicationToken().setTokenId(applicationToken); 170 171 ThriftClientProvider clientProvider = new ThriftClientProvider(() -> endpoint); 172 AromaClient aroma = new AromaClient(() -> clientProvider.get(), async, token); 173 return aroma; 174 175 } 176 177 private Endpoint createEndpoint() 178 { 179 TcpEndpoint tcpEndpoint = new TcpEndpoint(hostname, port); 180 181 Endpoint endpoint = new Endpoint(); 182 endpoint.setTcp(tcpEndpoint); 183 return endpoint; 184 } 185 186 } 187 188}