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}