diff options
author | 2018-03-26 13:49:56 -0700 | |
---|---|---|
committer | 2018-03-26 13:50:07 -0700 | |
commit | a20accc73189d8e5454cd26049c0e6fae75da16f (patch) | |
tree | 3526110a1f65591354eff6400383512df4828903 /cadi/client/src/main/java | |
parent | ac1e1ec76e9125206be91a2f32c7104c9392dc9a (diff) |
AT&T 2.0.19 Code drop, stage 2
Issue-ID: AAF-197
Change-Id: Ifc93308f52c10d6ad82e99cd3ff5ddb900bf219a
Signed-off-by: Instrumental <jcgmisc@stl.gathman.org>
Diffstat (limited to 'cadi/client/src/main/java')
23 files changed, 3757 insertions, 0 deletions
diff --git a/cadi/client/src/main/java/org/onap/aaf/cadi/client/AbsAuthentication.java b/cadi/client/src/main/java/org/onap/aaf/cadi/client/AbsAuthentication.java new file mode 100644 index 00000000..7a32473c --- /dev/null +++ b/cadi/client/src/main/java/org/onap/aaf/cadi/client/AbsAuthentication.java @@ -0,0 +1,130 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.cadi.client; + +import java.io.IOException; + +import org.onap.aaf.cadi.SecuritySetter; +import org.onap.aaf.cadi.Symm; +import org.onap.aaf.cadi.config.SecurityInfoC; + +/** + * AbsAuthentication is a class representing how to Authenticate onto a Client. + * + * Methods of setting Authentication on a Client vary, so CLIENT is a Generic Type + * This allows the ability to apply security onto Different Client Types, as they come + * into vogue, or change over time. + * + * Password is encrypted at rest. + * + * @author Jonathan + * + * @param <CLIENT> + */ +public abstract class AbsAuthentication<CLIENT> implements SecuritySetter<CLIENT> { + // HTTP Header for Authentication is "Authorization". This was from an early stage of internet where + // Access by Credential "Authorized" you for everything on the site. Since those early days, it became + // clear that "full access" wasn't appropriate, so the split between Authentication and Authorization + // came into being... But the Header remains. + public static final String AUTHORIZATION = "Authorization"; + private static final Symm symm; + + protected static final String REPEAT_OFFENDER = "This call is aborted because of repeated usage of invalid Passwords"; + private static final int MAX_TEMP_COUNT = 10; + private static final int MAX_SPAM_COUNT = 10000; + private static final long WAIT_TIME = 1000*60*4; + private final byte[] headValue; + private String user; + protected final SecurityInfoC<CLIENT> securityInfo; + protected long lastMiss; + protected int count; + + static { + try { + symm = Symm.encrypt.obtain(); + } catch (IOException e) { + throw new RuntimeException("Cannot create critical internal encryption key",e); + } + + } + + public AbsAuthentication(final SecurityInfoC<CLIENT> securityInfo, final String user, final byte[] headValue) throws IOException { + this.headValue = headValue==null?null:symm.encode(headValue); + this.user = user; + this.securityInfo = securityInfo; + lastMiss=0L; + count=0; + } + + protected String headValue() throws IOException { + if(headValue==null) { + return ""; + } else { + return new String(symm.decode(headValue)); + } + } + + protected void setUser(String id) { + user = id; + } + + @Override + public String getID() { + return user; + } + + public boolean isDenied() { + if(lastMiss>0 && lastMiss>System.currentTimeMillis()) { + return true; + } else { + lastMiss=0L; + return false; + } + } + + public synchronized int setLastResponse(int httpcode) { + if(httpcode == 401) { + ++count; + if(lastMiss==0L && count>MAX_TEMP_COUNT) { + lastMiss=System.currentTimeMillis()+WAIT_TIME; + } + // if(count>MAX_SPAM_COUNT) { + // System.err.printf("Your service has %d consecutive bad service logins to AAF. \nIt will now exit\n", + // count); + // System.exit(401); + // } + if(count%1000==0) { + System.err.printf("Your service has %d consecutive bad service logins to AAF. AAF Access will be disabled after %d\n", + count,MAX_SPAM_COUNT); + } + + } else { + lastMiss=0; + } + return count; + } + + public int count() { + return count; + } + +}
\ No newline at end of file diff --git a/cadi/client/src/main/java/org/onap/aaf/cadi/client/AbsTransferSS.java b/cadi/client/src/main/java/org/onap/aaf/cadi/client/AbsTransferSS.java new file mode 100644 index 00000000..3815bc67 --- /dev/null +++ b/cadi/client/src/main/java/org/onap/aaf/cadi/client/AbsTransferSS.java @@ -0,0 +1,76 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.cadi.client; + +import java.security.Principal; + +import org.onap.aaf.cadi.SecuritySetter; +import org.onap.aaf.cadi.config.SecurityInfoC; +import org.onap.aaf.cadi.principal.TaggedPrincipal; + +/** + * This client represents the ability to Transfer the Identity of the caller to the authenticated + * user being transferred to. This ability is critical for App-to-App communication to ensure that + * Authorization can happen on the End-Users' credentials when appropriate, even though Authentication + * to App1 by App2 must be by App2's credentials. + * + * @author Jonathan + * + * @param <CLIENT> + */ +public abstract class AbsTransferSS<CLIENT> implements SecuritySetter<CLIENT> { + protected String value; + protected SecurityInfoC<CLIENT> securityInfo; + protected SecuritySetter<CLIENT> defSS; + private Principal principal; + + //Format:<ID>:<APP>:<protocol>[:AS][,<ID>:<APP>:<protocol>]* + public AbsTransferSS(TaggedPrincipal principal, String app) { + init(principal, app); + } + + public AbsTransferSS(TaggedPrincipal principal, String app, SecurityInfoC<CLIENT> si) { + init(principal,app); + securityInfo = si; + this.defSS = si.defSS; + } + + private void init(TaggedPrincipal principal, String app) { + this.principal=principal; + if(principal==null) { + return; + } else { + value = principal.getName() + ':' + + app + ':' + + principal.tag() + ':' + + "AS"; + } + } + + /* (non-Javadoc) + * @see org.onap.aaf.cadi.SecuritySetter#getID() + */ + @Override + public String getID() { + return principal==null?"":principal.getName(); + } +} diff --git a/cadi/client/src/main/java/org/onap/aaf/cadi/client/BasicAuth.java b/cadi/client/src/main/java/org/onap/aaf/cadi/client/BasicAuth.java new file mode 100644 index 00000000..1eb8d7c4 --- /dev/null +++ b/cadi/client/src/main/java/org/onap/aaf/cadi/client/BasicAuth.java @@ -0,0 +1,28 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.cadi.client; + +/** + * Basic Auth is a marker Interface, because certain kinds of behaviors apply only to User/Password Combinations + */ +public interface BasicAuth { +} diff --git a/cadi/client/src/main/java/org/onap/aaf/cadi/client/EClient.java b/cadi/client/src/main/java/org/onap/aaf/cadi/client/EClient.java new file mode 100644 index 00000000..d5dfebf5 --- /dev/null +++ b/cadi/client/src/main/java/org/onap/aaf/cadi/client/EClient.java @@ -0,0 +1,51 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.cadi.client; + +import java.io.IOException; +import java.io.OutputStream; + +import javax.servlet.http.HttpServletResponse; + +import org.onap.aaf.misc.env.APIException; +import org.onap.aaf.misc.env.Data; +import org.onap.aaf.misc.rosetta.env.RosettaDF; + + +public interface EClient<CT> { + public void setMethod(String meth); + public void setPathInfo(String pathinfo); + public void setPayload(Transfer transfer); + public void addHeader(String tag, String value); + public void setQueryParams(String q); + public void setFragment(String f); + public void send() throws APIException; + public<T> Future<T> futureCreate(Class<T> t); + public Future<String> futureReadString(); + public<T> Future<T> futureRead(RosettaDF<T> df,Data.TYPE type); + public<T> Future<T> future(T t); + public Future<Void> future(HttpServletResponse resp, int expected) throws APIException; + + public interface Transfer { + public void transfer(OutputStream os) throws IOException, APIException; + } +} diff --git a/cadi/client/src/main/java/org/onap/aaf/cadi/client/Future.java b/cadi/client/src/main/java/org/onap/aaf/cadi/client/Future.java new file mode 100644 index 00000000..2579dc11 --- /dev/null +++ b/cadi/client/src/main/java/org/onap/aaf/cadi/client/Future.java @@ -0,0 +1,33 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.cadi.client; + +import org.onap.aaf.cadi.CadiException; + +public abstract class Future<T> { + public T value; + public abstract boolean get(int timeout) throws CadiException; + + public abstract int code(); + public abstract String body(); + public abstract String header(String tag); +}
\ No newline at end of file diff --git a/cadi/client/src/main/java/org/onap/aaf/cadi/client/Holder.java b/cadi/client/src/main/java/org/onap/aaf/cadi/client/Holder.java new file mode 100644 index 00000000..c13afc25 --- /dev/null +++ b/cadi/client/src/main/java/org/onap/aaf/cadi/client/Holder.java @@ -0,0 +1,46 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.cadi.client; + +/** + * Use to set Variables outside of Anonymous classes. + * @author Jonathan + * + * @param <T> + */ +public class Holder<T> { + private T value; + public Holder(T t) { + value = t; + } + public T set(T t) { + value = t; + return t; + } + + public T get() { + return value; + } + public String toString() { + return value.toString(); + } +} diff --git a/cadi/client/src/main/java/org/onap/aaf/cadi/client/Rcli.java b/cadi/client/src/main/java/org/onap/aaf/cadi/client/Rcli.java new file mode 100644 index 00000000..5ebc017e --- /dev/null +++ b/cadi/client/src/main/java/org/onap/aaf/cadi/client/Rcli.java @@ -0,0 +1,974 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.cadi.client; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.net.URI; +import java.util.Enumeration; + +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.onap.aaf.cadi.CadiException; +import org.onap.aaf.cadi.SecuritySetter; +import org.onap.aaf.cadi.client.EClient.Transfer; +import org.onap.aaf.misc.env.APIException; +import org.onap.aaf.misc.env.Data.TYPE; +import org.onap.aaf.misc.env.util.Pool; +import org.onap.aaf.misc.env.util.Pool.Pooled; +import org.onap.aaf.misc.rosetta.env.RosettaDF; + +public abstract class Rcli<CT> { + public static final String FORM_ENCODED = "application/x-www-form-urlencoded"; + public static final String APPL_JSON = "application/json"; + public static final String APPL_XML = "application/xml"; + public static final String BLANK = ""; + public static final String CONTENT_TYPE = "Content-Type"; + public static final String ACCEPT = "Accept"; + + protected static final String POST = "POST"; + protected static final String GET = "GET"; + protected static final String PUT = "PUT"; + protected static final String DELETE = "DELETE"; + protected TYPE type; + protected String apiVersion; + protected int readTimeout = 5000; + protected int connectionTimeout = 3000; + protected URI uri; + private String queryParams, fragment; + public static Pool<byte[]> buffPool = new Pool<byte[]>(new Pool.Creator<byte[]>() { + @Override + public byte[] create() throws APIException { + return new byte[1024]; + } + + @Override + public void destroy(byte[] t) { + } + + @Override + public boolean isValid(byte[] t) { + return true; + } + + @Override + public void reuse(byte[] t) { + } + }); + + + public Rcli() { + super(); + } + + public abstract void setSecuritySetter(SecuritySetter<CT> ss); + public abstract SecuritySetter<CT> getSecuritySetter(); + + + public Rcli<CT> forUser(SecuritySetter<CT> ss) { + Rcli<CT> rv = clone(uri==null?this.uri:uri,ss); + setSecuritySetter(ss); + rv.type = type; + rv.apiVersion = apiVersion; + return rv; + } + + protected abstract Rcli<CT> clone(URI uri, SecuritySetter<CT> ss); + + public abstract void invalidate() throws CadiException; + + public Rcli<CT> readTimeout(int millis) { + readTimeout = millis; + return this; + } + + public Rcli<CT> connectionTimeout(int millis) { + connectionTimeout = millis; + return this; + } + + public Rcli<CT> type(TYPE type) { + this.type=type; + return this; + } + + public Rcli<CT> apiVersion(String apiVersion) { + this.apiVersion = apiVersion; + return this; + } + + public boolean isApiVersion(String prospective) { + return apiVersion.equals(prospective); + } + + + public String typeString(Class<?> cls) { + return "application/"+cls.getSimpleName()+"+"+type.name().toLowerCase()+ + (apiVersion==null?BLANK:";version="+apiVersion); + } + + protected abstract EClient<CT> client() throws CadiException; + + + public<T> Future<T> create(String pathinfo, String contentType, final RosettaDF<T> df, final T t) throws APIException, CadiException { + final String qp; + if(pathinfo==null) { + qp=queryParams; + } else { + final int idx = pathinfo.indexOf('?'); + if(idx>=0) { + qp=pathinfo.substring(idx+1); + pathinfo=pathinfo.substring(0,idx); + } else { + qp=queryParams; + } + } + + EClient<CT> client = client(); + client.setMethod(POST); + client.addHeader(CONTENT_TYPE,contentType); + client.setPathInfo(pathinfo); + client.setQueryParams(qp); + client.setFragment(fragment); + client.setPayload(new EClient.Transfer() { + @Override + public void transfer(OutputStream os) throws IOException, APIException { + df.newData().out(type).direct(t,os); + } + }); + client.send(); + queryParams = fragment = null; + return client.futureCreate(df.getTypeClass()); + } + + public<T> Future<T> create(String pathinfo, final RosettaDF<T> df, final T t) throws APIException, CadiException { + final String qp; + if(pathinfo==null) { + qp=queryParams; + } else { + final int idx = pathinfo.indexOf('?'); + if(idx>=0) { + qp=pathinfo.substring(idx+1); + pathinfo=pathinfo.substring(0,idx); + } else { + qp=queryParams; + } + } + + EClient<CT> client = client(); + client.setMethod(POST); + client.addHeader(CONTENT_TYPE,typeString(df.getTypeClass())); + client.setPathInfo(pathinfo); + client.setQueryParams(qp); + client.setFragment(fragment); + client.setPayload(new EClient.Transfer() { + @Override + public void transfer(OutputStream os) throws IOException, APIException { + df.newData().out(type).direct(t,os); + } + }); + client.send(); + queryParams = fragment = null; + return client.futureCreate(df.getTypeClass()); + } + + public<T> Future<T> create(String pathinfo, Class<?> cls, final RosettaDF<T> df, final T t) throws APIException, CadiException { + final String qp; + if(pathinfo==null) { + qp=queryParams; + } else { + final int idx = pathinfo.indexOf('?'); + if(idx>=0) { + qp=pathinfo.substring(idx+1); + pathinfo=pathinfo.substring(0,idx); + } else { + qp=queryParams; + } + } + + EClient<CT> client = client(); + client.setMethod(POST); + client.addHeader(CONTENT_TYPE,typeString(cls)); + client.setPathInfo(pathinfo); + client.setQueryParams(qp); + client.setFragment(fragment); + client.setPayload(new EClient.Transfer() { + @Override + public void transfer(OutputStream os) throws IOException, APIException { + df.newData().out(type).direct(t,os); + } + }); + client.send(); + queryParams = fragment = null; + return client.futureCreate(df.getTypeClass()); + } + + public<T> Future<T> create(String pathinfo, Class<T> cls) throws APIException, CadiException { + final String qp; + if(pathinfo==null) { + qp=queryParams; + } else { + final int idx = pathinfo.indexOf('?'); + if(idx>=0) { + qp=pathinfo.substring(idx+1); + pathinfo=pathinfo.substring(0,idx); + } else { + qp=queryParams; + } + } + + + EClient<CT> client = client(); + client.setMethod(POST); + client.addHeader(CONTENT_TYPE,typeString(cls)); + client.setPathInfo(pathinfo); + client.setQueryParams(qp); + client.setFragment(fragment); + client.setPayload(null); + client.send(); + queryParams = fragment = null; + return client.futureCreate(cls); + } + + public Future<Void> create(String pathinfo, String contentType) throws APIException, CadiException { + final String qp; + if(pathinfo==null) { + qp=queryParams; + } else { + final int idx = pathinfo.indexOf('?'); + if(idx>=0) { + qp=pathinfo.substring(idx+1); + pathinfo=pathinfo.substring(0,idx); + } else { + qp=queryParams; + } + } + + + EClient<CT> client = client(); + client.setMethod(POST); + client.addHeader(CONTENT_TYPE,contentType); + client.setPathInfo(pathinfo); + client.setQueryParams(qp); + client.setFragment(fragment); + client.setPayload(null); + client.send(); + queryParams = fragment = null; + return client.futureCreate(Void.class); + } + + + /** + * Post Data in WWW expected format, with the format tag1=value1&tag2=value2, etc + * Note Shortcut: + * Because typically, you will want to have a variable as value, you can type, as long as tag ends with "=" + * postForm(..., "tag1=value1","tag2=",var2); + * @param pathinfo + * @param df + * @param cls + * @param formParam + * @return + * @throws APIException + * @throws CadiException + */ + public <T> Future<T> postForm(String pathinfo, final RosettaDF<T> df, final String ... formParam) throws APIException, CadiException { + final String qp; + if(pathinfo==null) { + qp=queryParams; + } else { + final int idx = pathinfo.indexOf('?'); + if(idx>=0) { + qp=pathinfo.substring(idx+1); + pathinfo=pathinfo.substring(0,idx); + } else { + qp=queryParams; + } + } + + EClient<CT> client = client(); + client.setMethod(POST); + client.addHeader(CONTENT_TYPE,FORM_ENCODED); + switch(type) { + case JSON: + client.addHeader(ACCEPT, APPL_JSON); + break; + case XML: + client.addHeader(ACCEPT, APPL_XML); + break; + default: + break; + } + client.setPathInfo(pathinfo); + client.setQueryParams(qp); + client.setFragment(fragment); + client.setPayload(new Transfer() { + @Override + public void transfer(OutputStream os) throws IOException, APIException { + PrintStream ps; + if(os instanceof PrintStream) { + ps = (PrintStream)os; + } else { + ps = new PrintStream(os); + } + boolean first = true; + for(String fp : formParam) { + if(fp!=null) { + if(first) { + first = false; + } else { + ps.print('&'); + } + if(fp.endsWith("=")) { + first = true; + } + ps.print(fp); + } + } + }}); + client.send(); + queryParams = fragment = null; + return client.futureRead(df,TYPE.JSON); + } + + /** + * Read String, using POST for keyInfo + * + * @param pathinfo + * @param df + * @param t + * @param resp + * @return + * @throws APIException + * @throws CadiException + */ + public<T> Future<String> readPost(String pathinfo, final RosettaDF<T> df, final T t) throws APIException, CadiException { + final String qp; + if(pathinfo==null) { + qp=queryParams; + } else { + final int idx = pathinfo.indexOf('?'); + if(idx>=0) { + qp=pathinfo.substring(idx+1); + pathinfo=pathinfo.substring(0,idx); + } else { + qp=queryParams; + } + } + + EClient<CT> client = client(); + client.setMethod(POST); + client.addHeader(CONTENT_TYPE,typeString(df.getTypeClass())); + client.setPathInfo(pathinfo); + client.setQueryParams(qp); + client.setFragment(fragment); + client.setPayload(new EClient.Transfer() { + @Override + public void transfer(OutputStream os) throws IOException, APIException { + df.newData().out(type).direct(t,os); + } + }); + client.send(); + queryParams = fragment = null; + return client.futureReadString(); + } + + /** + * Read using POST for keyInfo, responding with marshaled Objects + * + * @param pathinfo + * @param df + * @param t + * @param resp + * @return + * @throws APIException + * @throws CadiException + */ + public<T,R> Future<R> readPost(String pathinfo, final RosettaDF<T> df, final T t, final RosettaDF<R> resp) throws APIException, CadiException { + final String qp; + if(pathinfo==null) { + qp=queryParams; + } else { + final int idx = pathinfo.indexOf('?'); + if(idx>=0) { + qp=pathinfo.substring(idx+1); + pathinfo=pathinfo.substring(0,idx); + } else { + qp=queryParams; + } + } + + EClient<CT> client = client(); + client.setMethod(POST); + client.addHeader(CONTENT_TYPE,typeString(df.getTypeClass())); + client.setPathInfo(pathinfo); + client.setQueryParams(qp); + client.setFragment(fragment); + client.setPayload(new EClient.Transfer() { + @Override + public void transfer(OutputStream os) throws IOException, APIException { + df.newData().out(type).direct(t,os); + } + }); + client.send(); + queryParams = fragment = null; + return client.futureRead(resp,resp.getOutType()); + } + + public Future<String> readPost(String pathinfo, String contentType, String ... headers) throws CadiException, APIException { + final String qp; + if(pathinfo==null) { + qp=queryParams; + } else { + final int idx = pathinfo.indexOf('?'); + if(idx>=0) { + qp=pathinfo.substring(idx+1); + pathinfo=pathinfo.substring(0,idx); + } else { + qp=queryParams; + } + } + + EClient<CT> client = client(); + client.setMethod(POST); + client.addHeader(CONTENT_TYPE,contentType); + client.setPathInfo(pathinfo); + client.setQueryParams(qp); + client.setFragment(fragment); + client.setPayload(new EClient.Transfer() { + @Override + public void transfer(OutputStream os) throws IOException, APIException { + }}); + client.send(); + queryParams = fragment = null; + return client.futureReadString(); + } + + public Future<String> read(String pathinfo, String accept, String ... headers) throws APIException, CadiException { + final String qp; + if(pathinfo==null) { + qp=queryParams; + } else { + final int idx = pathinfo.indexOf('?'); + if(idx>=0) { + qp=pathinfo.substring(idx+1); + pathinfo=pathinfo.substring(0,idx); + } else { + qp=queryParams; + } + } + + EClient<CT> client = client(); + client.setMethod(GET); + client.addHeader(ACCEPT, accept); + + for(int i=1;i<headers.length;i=i+2) { + client.addHeader(headers[i-1],headers[i]); + } + client.setQueryParams(qp); + client.setFragment(fragment); + + client.setPathInfo(pathinfo); + + client.setPayload(null); + client.send(); + queryParams = fragment = null; + return client.futureReadString(); + } + + public<T> Future<T> read(String pathinfo, String accept, RosettaDF<T> df, String ... headers) throws APIException, CadiException { + final String qp; + if(pathinfo==null) { + qp=queryParams; + } else { + final int idx = pathinfo.indexOf('?'); + if(idx>=0) { + qp=pathinfo.substring(idx+1); + pathinfo=pathinfo.substring(0,idx); + } else { + qp=queryParams; + } + } + + + EClient<CT> client = client(); + client.setMethod(GET); + client.addHeader(ACCEPT, accept); + for(int i=1;i<headers.length;i=i+2) { + client.addHeader(headers[i-1],headers[i]); + } + client.setQueryParams(qp); + client.setFragment(fragment); + client.setPathInfo(pathinfo); + + client.setPayload(null); + client.send(); + queryParams = fragment = null; + return client.futureRead(df,type); + } + + public<T> Future<T> read(String pathinfo, RosettaDF<T> df,String ... headers) throws APIException, CadiException { + final String qp; + if(pathinfo==null) { + qp=queryParams; + } else { + final int idx = pathinfo.indexOf('?'); + if(idx>=0) { + qp=pathinfo.substring(idx+1); + pathinfo=pathinfo.substring(0,idx); + } else { + qp=queryParams; + } + } + + + EClient<CT> client = client(); + client.setMethod(GET); + client.addHeader(ACCEPT, typeString(df.getTypeClass())); + for(int i=1;i<headers.length;i=i+2) { + client.addHeader(headers[i-1],headers[i]); + } + client.setQueryParams(qp); + client.setFragment(fragment); + client.setPathInfo(pathinfo); + + client.setPayload(null); + client.send(); + queryParams = fragment = null; + return client.futureRead(df,type); + } + + public<T> Future<T> read(String pathinfo, Class<?> cls, RosettaDF<T> df) throws APIException, CadiException { + final String qp; + if(pathinfo==null) { + qp=queryParams; + } else { + final int idx = pathinfo.indexOf('?'); + if(idx>=0) { + qp=pathinfo.substring(idx+1); + pathinfo=pathinfo.substring(0,idx); + } else { + qp=queryParams; + } + } + + EClient<CT> client = client(); + client.setMethod(GET); + client.addHeader(ACCEPT, typeString(cls)); + client.setQueryParams(qp); + client.setFragment(fragment); + client.setPathInfo(pathinfo); + + client.setPayload(null); + client.send(); + queryParams = fragment = null; + return client.futureRead(df,type); + } + + public<T> Future<T> update(String pathinfo, String contentType, final RosettaDF<T> df, final T t) throws APIException, CadiException { + final String qp; + if(pathinfo==null) { + qp=queryParams; + } else { + final int idx = pathinfo.indexOf('?'); + if(idx>=0) { + qp=pathinfo.substring(idx+1); + pathinfo=pathinfo.substring(0,idx); + } else { + qp=queryParams; + } + } + + + EClient<CT> client = client(); + client.setMethod(PUT); + client.addHeader(CONTENT_TYPE,contentType); + client.setQueryParams(qp); + client.setFragment(fragment); + client.setPathInfo(pathinfo); + client.setPayload(new EClient.Transfer() { + @Override + public void transfer(OutputStream os) throws IOException, APIException { + df.newData().out(type).direct(t,os); + } + }); + client.send(); + queryParams = fragment = null; + return client.future(t); + } + + public<T> Future<String> updateRespondString(String pathinfo, final RosettaDF<T> df, final T t) throws APIException, CadiException { + final String qp; + if(pathinfo==null) { + qp=queryParams; + } else { + final int idx = pathinfo.indexOf('?'); + if(idx>=0) { + qp=pathinfo.substring(idx+1); + pathinfo=pathinfo.substring(0,idx); + } else { + qp=queryParams; + } + } + + + EClient<CT> client = client(); + client.setMethod(PUT); + client.addHeader(CONTENT_TYPE, typeString(df.getTypeClass())); + client.setQueryParams(qp); + client.setFragment(fragment); + client.setPathInfo(pathinfo); + client.setPayload(new EClient.Transfer() { + @Override + public void transfer(OutputStream os) throws IOException, APIException { + df.newData().out(type).direct(t,os); + } + }); + client.send(); + queryParams = fragment = null; + return client.futureReadString(); + } + + + public<T> Future<T> update(String pathinfo, final RosettaDF<T> df, final T t) throws APIException, CadiException { + final String qp; + if(pathinfo==null) { + qp=queryParams; + } else { + final int idx = pathinfo.indexOf('?'); + if(idx>=0) { + qp=pathinfo.substring(idx+1); + pathinfo=pathinfo.substring(0,idx); + } else { + qp=queryParams; + } + } + + EClient<CT> client = client(); + client.setMethod(PUT); + client.addHeader(CONTENT_TYPE, typeString(df.getTypeClass())); + client.setQueryParams(qp); + client.setFragment(fragment); + client.setPathInfo(pathinfo); + client.setPayload(new EClient.Transfer() { + @Override + public void transfer(OutputStream os) throws IOException, APIException { + df.newData().out(type).direct(t,os); + } + }); + client.send(); + queryParams = fragment = null; + return client.future(t); + } + + public<T> Future<T> update(String pathinfo, Class<?> cls, final RosettaDF<T> df, final T t) throws APIException, CadiException { + final String qp; + if(pathinfo==null) { + qp=queryParams; + } else { + final int idx = pathinfo.indexOf('?'); + if(idx>=0) { + qp=pathinfo.substring(idx+1); + pathinfo=pathinfo.substring(0,idx); + } else { + qp=queryParams; + } + } + + + EClient<CT> client = client(); + client.setMethod(PUT); + client.addHeader(CONTENT_TYPE, typeString(cls)); + client.setQueryParams(qp); + client.setFragment(fragment); + client.setPathInfo(pathinfo); + client.setPayload(new EClient.Transfer() { + @Override + public void transfer(OutputStream os) throws IOException, APIException { + df.newData().out(type).direct(t,os); + } + }); + client.send(); + queryParams = fragment = null; + return client.future(t); + } + + /** + * A method to update with a VOID + * @param pathinfo + * @param resp + * @param expected + * @return + * @throws APIException + * @throws CadiException + */ + public<T> Future<Void> update(String pathinfo) throws APIException, CadiException { + final String qp; + if(pathinfo==null) { + qp=queryParams; + } else { + final int idx = pathinfo.indexOf('?'); + if(idx>=0) { + qp=pathinfo.substring(idx+1); + pathinfo=pathinfo.substring(0,idx); + } else { + qp=queryParams; + } + } + + + EClient<CT> client = client(); + client.setMethod(PUT); + client.addHeader(CONTENT_TYPE, typeString(Void.class)); + client.setQueryParams(qp); + client.setFragment(fragment); + client.setPathInfo(pathinfo); +// client.setPayload(new EClient.Transfer() { +// @Override +// public void transfer(OutputStream os) throws IOException, APIException { +// } +// }); + client.send(); + queryParams = fragment = null; + return client.future(null); + } + + public<T> Future<T> delete(String pathinfo, String contentType, final RosettaDF<T> df, final T t) throws APIException, CadiException { + final String qp; + if(pathinfo==null) { + qp=queryParams; + } else { + final int idx = pathinfo.indexOf('?'); + if(idx>=0) { + qp=pathinfo.substring(idx+1); + pathinfo=pathinfo.substring(0,idx); + } else { + qp=queryParams; + } + } + + + EClient<CT> client = client(); + client.setMethod(DELETE); + client.addHeader(CONTENT_TYPE, contentType); + client.setQueryParams(qp); + client.setFragment(fragment); + client.setPathInfo(pathinfo); + client.setPayload(new EClient.Transfer() { + @Override + public void transfer(OutputStream os) throws IOException, APIException { + df.newData().out(type).direct(t,os); + } + }); + client.send(); + queryParams = fragment = null; + return client.future(t); + } + + public<T> Future<T> delete(String pathinfo, Class<?> cls, final RosettaDF<T> df, final T t) throws APIException, CadiException { + final String qp; + if(pathinfo==null) { + qp=queryParams; + } else { + final int idx = pathinfo.indexOf('?'); + if(idx>=0) { + qp=pathinfo.substring(idx+1); + pathinfo=pathinfo.substring(0,idx); + } else { + qp=queryParams; + } + } + + + EClient<CT> client = client(); + client.setMethod(DELETE); + client.addHeader(CONTENT_TYPE, typeString(cls)); + client.setQueryParams(qp); + client.setFragment(fragment); + client.setPathInfo(pathinfo); + client.setPayload(new EClient.Transfer() { + @Override + public void transfer(OutputStream os) throws IOException, APIException { + df.newData().out(type).direct(t,os); + } + }); + client.send(); + queryParams = fragment = null; + return client.future(t); + } + + public<T> Future<T> delete(String pathinfo, final RosettaDF<T> df, final T t) throws APIException, CadiException { + final String qp; + if(pathinfo==null) { + qp=queryParams; + } else { + final int idx = pathinfo.indexOf('?'); + if(idx>=0) { + qp=pathinfo.substring(idx+1); + pathinfo=pathinfo.substring(0,idx); + } else { + qp=queryParams; + } + } + + EClient<CT> client = client(); + client.setMethod(DELETE); + client.addHeader(CONTENT_TYPE, typeString(df.getTypeClass())); + client.setQueryParams(qp); + client.setFragment(fragment); + client.setPathInfo(pathinfo); + client.setPayload(new EClient.Transfer() { + @Override + public void transfer(OutputStream os) throws IOException, APIException { + df.newData().out(type).direct(t,os); + } + }); + + client.send(); + queryParams = fragment = null; + return client.future(t); + } + + + public<T> Future<T> delete(String pathinfo, Class<T> cls) throws APIException, CadiException { + final String qp; + if(pathinfo==null) { + qp=queryParams; + } else { + final int idx = pathinfo.indexOf('?'); + if(idx>=0) { + qp=pathinfo.substring(idx+1); + pathinfo=pathinfo.substring(0,idx); + } else { + qp=queryParams; + } + } + + + EClient<CT> client = client(); + client.setMethod(DELETE); + client.addHeader(CONTENT_TYPE, typeString(cls)); + client.setQueryParams(qp); + client.setFragment(fragment); + client.setPathInfo(pathinfo); + client.setPayload(null); + client.send(); + queryParams = fragment = null; + return client.future((T)null); + } + + public Future<Void> delete(String pathinfo, String contentType) throws APIException, CadiException { + final String qp; + if(pathinfo==null) { + qp=queryParams; + } else { + final int idx = pathinfo.indexOf('?'); + if(idx>=0) { + qp=pathinfo.substring(idx+1); + pathinfo=pathinfo.substring(0,idx); + } else { + qp=queryParams; + } + } + + EClient<CT> client = client(); + client.setMethod(DELETE); + client.addHeader(CONTENT_TYPE, contentType); + client.setQueryParams(qp); + client.setFragment(fragment); + client.setPathInfo(pathinfo); + client.setPayload(null); + client.send(); + queryParams = fragment = null; + return client.future(null); + } + + public Future<Void> transfer(final HttpServletRequest req, final HttpServletResponse resp, final String pathParam, final int expected) throws CadiException, APIException { + EClient<CT> client = client(); + URI uri; + try { + uri = new URI(req.getRequestURI()); + } catch (Exception e) { + throw new CadiException("Invalid incoming URI",e); + } + String name; + for(Enumeration<String> en = req.getHeaderNames();en.hasMoreElements();) { + name = en.nextElement(); + client.addHeader(name,req.getHeader(name)); + } + client.setQueryParams(req.getQueryString()); + client.setFragment(uri.getFragment()); + client.setPathInfo(pathParam); + String meth = req.getMethod(); + client.setMethod(meth); + if(!"GET".equals(meth)) { + client.setPayload(new EClient.Transfer() { + @Override + public void transfer(OutputStream os) throws IOException, APIException { + final ServletInputStream is = req.getInputStream(); + int read; + // reuse Buffers + Pooled<byte[]> pbuff = buffPool.get(); + try { + while((read=is.read(pbuff.content))>=0) { + os.write(pbuff.content,0,read); + } + } finally { + pbuff.done(); + } + } + }); + } + client.send(); + return client.future(resp, expected); + } + + public String toString() { + return uri.toString(); + } + + /** + * @param queryParams the queryParams to set + * @return + */ + public Rcli<CT> setQueryParams(String queryParams) { + this.queryParams = queryParams; + return this; + } + + + /** + * @param fragment the fragment to set + * @return + */ + public Rcli<CT> setFragment(String fragment) { + this.fragment = fragment; + return this; + } + + public URI getURI() { + return uri; + } + +}
\ No newline at end of file diff --git a/cadi/client/src/main/java/org/onap/aaf/cadi/client/Result.java b/cadi/client/src/main/java/org/onap/aaf/cadi/client/Result.java new file mode 100644 index 00000000..fecb847b --- /dev/null +++ b/cadi/client/src/main/java/org/onap/aaf/cadi/client/Result.java @@ -0,0 +1,60 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.cadi.client; + +public class Result<T> { + public final int code; + public final T value; + public final String error; + + private Result(int code, T value, String error) { + this.code = code; + this.value = value; + this.error = error; + } + + public static<T> Result<T> ok(int code,T t) { + return new Result<T>(code,t,null); + } + + public static<T> Result<T> err(int code,String body) { + return new Result<T>(code,null,body); + } + + public static<T> Result<T> err(Result<?> r) { + return new Result<T>(r.code,null,r.error); + } + + public boolean isOK() { + return error==null; + } + + public String toString() { + StringBuilder sb = new StringBuilder("Code: "); + sb.append(code); + if(error!=null) { + sb.append(" = "); + sb.append(error); + } + return sb.toString(); + } +}
\ No newline at end of file diff --git a/cadi/client/src/main/java/org/onap/aaf/cadi/client/Retryable.java b/cadi/client/src/main/java/org/onap/aaf/cadi/client/Retryable.java new file mode 100644 index 00000000..8208efe1 --- /dev/null +++ b/cadi/client/src/main/java/org/onap/aaf/cadi/client/Retryable.java @@ -0,0 +1,71 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.cadi.client; + +import java.net.ConnectException; + +import org.onap.aaf.cadi.CadiException; +import org.onap.aaf.cadi.Locator; +import org.onap.aaf.misc.env.APIException; + +/** + * + * @author Jonathan + * + * @param <RT> + * @param <RET> + */ +public abstract class Retryable<RET> { + // be able to hold state for consistent Connections. Not required for all connection types. + public Rcli<?> lastClient; + private Locator.Item item; + + public Retryable() { + lastClient = null; + item = null; + } + + public Retryable(Retryable<?> ret) { + lastClient = ret.lastClient; + item = ret.item; + } + + public Locator.Item item(Locator.Item item) { + lastClient = null; + this.item = item; + return item; + } + public Locator.Item item() { + return item; + } + + public abstract RET code(Rcli<?> client) throws CadiException, ConnectException, APIException; + + /** + * Note, Retryable is tightly coupled to the Client Utilizing. It will not be the wrong type. + * @return + */ + @SuppressWarnings("unchecked") + public <CLIENT> Rcli<CLIENT> lastClient() { + return (Rcli<CLIENT>)lastClient; + } +} diff --git a/cadi/client/src/main/java/org/onap/aaf/cadi/http/HAuthorizationHeader.java b/cadi/client/src/main/java/org/onap/aaf/cadi/http/HAuthorizationHeader.java new file mode 100644 index 00000000..787c5c29 --- /dev/null +++ b/cadi/client/src/main/java/org/onap/aaf/cadi/http/HAuthorizationHeader.java @@ -0,0 +1,54 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.cadi.http; + +import java.io.IOException; +import java.net.HttpURLConnection; + +import javax.net.ssl.HttpsURLConnection; + +import org.onap.aaf.cadi.CadiException; +import org.onap.aaf.cadi.client.AbsAuthentication; +import org.onap.aaf.cadi.config.SecurityInfoC; + +public class HAuthorizationHeader extends AbsAuthentication<HttpURLConnection> { + + public HAuthorizationHeader(SecurityInfoC<HttpURLConnection> si, String user, String headValue) throws IOException { + super(si,user,headValue==null?null:headValue.getBytes()); + } + + @Override + public void setSecurity(HttpURLConnection huc) throws CadiException { + if(isDenied()) { + throw new CadiException(REPEAT_OFFENDER); + } + try { + huc.addRequestProperty(AUTHORIZATION , headValue()); + } catch (IOException e) { + throw new CadiException(e); + } + if(securityInfo!=null && huc instanceof HttpsURLConnection) { + securityInfo.setSocketFactoryOn((HttpsURLConnection)huc); + } + } + +} diff --git a/cadi/client/src/main/java/org/onap/aaf/cadi/http/HBasicAuthSS.java b/cadi/client/src/main/java/org/onap/aaf/cadi/http/HBasicAuthSS.java new file mode 100644 index 00000000..9e86c7fb --- /dev/null +++ b/cadi/client/src/main/java/org/onap/aaf/cadi/http/HBasicAuthSS.java @@ -0,0 +1,68 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.cadi.http; + +import java.io.IOException; +import java.net.HttpURLConnection; + +import org.onap.aaf.cadi.Symm; +import org.onap.aaf.cadi.client.BasicAuth; +import org.onap.aaf.cadi.config.Config; +import org.onap.aaf.cadi.config.SecurityInfoC; +import org.onap.aaf.cadi.principal.BasicPrincipal; + +public class HBasicAuthSS extends HAuthorizationHeader implements BasicAuth { + public HBasicAuthSS(SecurityInfoC<HttpURLConnection> si, String user, String password) throws IOException { + super(si, user, "Basic " + Symm.base64noSplit.encode(user + ':' + password)); + } + + public HBasicAuthSS(SecurityInfoC<HttpURLConnection> si) throws IOException { + this(si,si.access.getProperty(Config.AAF_APPID, null), + si.access.decrypt(si.access.getProperty(Config.AAF_APPPASS, null), false)); + } + + public HBasicAuthSS(SecurityInfoC<HttpURLConnection> si, boolean setDefault) throws IOException { + this(si,si.access.getProperty(Config.AAF_APPID, null), + si.access.decrypt(si.access.getProperty(Config.AAF_APPPASS, null), false),setDefault); + } + + + public HBasicAuthSS(SecurityInfoC<HttpURLConnection> si, String user, String pass, boolean asDefault) throws IOException { + this(si, user,pass); + if(asDefault) { + si.set(this); + } + } + + public HBasicAuthSS(BasicPrincipal bp, SecurityInfoC<HttpURLConnection> si) throws IOException { + this(si, bp.getName(),new String(bp.getCred())); + } + + public HBasicAuthSS(BasicPrincipal bp, SecurityInfoC<HttpURLConnection> si, boolean asDefault) throws IOException { + this(si, bp.getName(),new String(bp.getCred())); + if(asDefault) { + si.set(this); + } + } + + +} diff --git a/cadi/client/src/main/java/org/onap/aaf/cadi/http/HClient.java b/cadi/client/src/main/java/org/onap/aaf/cadi/http/HClient.java new file mode 100644 index 00000000..46099887 --- /dev/null +++ b/cadi/client/src/main/java/org/onap/aaf/cadi/http/HClient.java @@ -0,0 +1,444 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.cadi.http; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.Reader; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URL; +import java.util.ArrayList; + +import javax.servlet.http.HttpServletResponse; + +import org.onap.aaf.cadi.CadiException; +import org.onap.aaf.cadi.LocatorException; +import org.onap.aaf.cadi.SecuritySetter; +import org.onap.aaf.cadi.client.EClient; +import org.onap.aaf.cadi.client.Future; +import org.onap.aaf.cadi.client.Rcli; +import org.onap.aaf.misc.env.APIException; +import org.onap.aaf.misc.env.Data; +import org.onap.aaf.misc.env.Data.TYPE; +import org.onap.aaf.misc.env.util.Pool.Pooled; +import org.onap.aaf.misc.rosetta.env.RosettaDF; + +/** + * Low Level Http Client Mechanism. Chances are, you want the high level "HRcli" + * for Rosetta Object Translation + * + * @author Jonathan + * + */ +public class HClient implements EClient<HttpURLConnection> { + private URI uri; + private ArrayList<Header> headers; + private String meth; + private String pathinfo; + private String query; + private String fragment; + private Transfer transfer; + private SecuritySetter<HttpURLConnection> ss; + private HttpURLConnection huc; + private int connectTimeout; + + public HClient(SecuritySetter<HttpURLConnection> ss, URI uri,int connectTimeout) throws LocatorException { + if (uri == null) { + throw new LocatorException("No Service available to call"); + } + this.uri = uri; + this.ss = ss; + this.connectTimeout = connectTimeout; + pathinfo = query = fragment = null; + } + + @Override + public void setMethod(String meth) { + this.meth = meth; + } + + @Override + public void setPathInfo(String pathinfo) { + this.pathinfo = pathinfo; + } + + @Override + public void setPayload(Transfer transfer) { + this.transfer = transfer; + } + + @Override + public void addHeader(String tag, String value) { + if (headers == null) + headers = new ArrayList<Header>(); + headers.add(new Header(tag, value)); + } + + @Override + public void setQueryParams(String q) { + query = q; + } + + @Override + public void setFragment(String f) { + fragment = f; + } + + @Override + public void send() throws APIException { + try { + // Build URL from given URI plus current Settings + if(uri.getPath()==null) { + throw new APIException("Invalid URL entered for HClient"); + } + StringBuilder pi=null; + if(pathinfo!=null) { // additional pathinfo + pi = new StringBuilder(uri.getPath()); + if(!pathinfo.startsWith("/")) { + pi.append('/'); + } + pi.append(pathinfo); + } + URL url = new URI( + uri.getScheme(), + uri.getUserInfo(), + uri.getHost(), + uri.getPort(), + pi==null?uri.getPath():pi.toString(), + query, + fragment).toURL(); + pathinfo=null; + query=null; + fragment=null; + huc = (HttpURLConnection) url.openConnection(); + huc.setRequestMethod(meth); + if(ss!=null) { + ss.setSecurity(huc); + } + if (headers != null) + for (Header d : headers) { + huc.addRequestProperty(d.tag, d.value); + } + huc.setDoInput(true); + huc.setDoOutput(true); + huc.setUseCaches(false); + huc.setConnectTimeout(connectTimeout); + huc.connect(); + if (transfer != null) { + transfer.transfer(huc.getOutputStream()); + } + // TODO other settings? There's a bunch here. + } catch (Exception e) { + throw new APIException(e); + } finally { // ensure all these are reset after sends + meth=pathinfo=null; + if(headers!=null) { + headers.clear(); + } + pathinfo = query = fragment = ""; + } + } + + public URI getURI() { + return uri; + } + + public int timeout() { + return connectTimeout; + } + + public abstract class HFuture<T> extends Future<T> { + protected HttpURLConnection huc; + protected int respCode; + protected String respMessage; + protected IOException exception; + protected StringBuilder errContent; + + public HFuture(final HttpURLConnection huc) { + this.huc = huc; + } + + protected boolean evalInfo(HttpURLConnection huc) throws APIException, IOException{ + return respCode == 200; + }; + + @Override + public final boolean get(int timeout) throws CadiException { + try { + huc.setReadTimeout(timeout); + respCode = huc.getResponseCode(); + ss.setLastResponse(respCode); + if(evalInfo(huc)) { + return true; + } else { + extractError(); + return false; + } + } catch (IOException | APIException e) { + throw new CadiException(e); + } finally { + close(); + } + } + + private void extractError() { + InputStream is = huc.getErrorStream(); + try { + if(is==null) { + is = huc.getInputStream(); + } + if(is!=null) { + errContent = new StringBuilder(); + int c; + while((c=is.read())>=0) { + errContent.append((char)c); + } + } + } catch (IOException e) { + exception = e; + } + } + + // Typically only used by Read + public StringBuilder inputStreamToString(InputStream is) { + // Avoids Carriage returns, and is reasonably efficient, given + // the buffer reads. + try { + StringBuilder sb = new StringBuilder(); + Reader rdr = new InputStreamReader(is); + try { + char[] buf = new char[256]; + int read; + while ((read = rdr.read(buf)) >= 0) { + sb.append(buf, 0, read); + } + } finally { + rdr.close(); + } + return sb; + } catch (IOException e) { + exception = e; + return null; + } + } + + + @Override + public int code() { + return respCode; + } + + public HttpURLConnection huc() { + return huc; + } + + public IOException exception() { + return exception; + } + + public String respMessage() { + return respMessage; + } + + @Override + public String header(String tag) { + return huc.getHeaderField(tag); + } + + public void close() { + if(huc!=null) { + huc.disconnect(); + } + } + } + + @Override + public <T> Future<T> futureCreate(Class<T> t) { + return new HFuture<T>(huc) { + public boolean evalInfo(HttpURLConnection huc) { + return respCode==201; + } + + @Override + public String body() { + if (errContent != null) { + return errContent.toString(); + + } else if (respMessage != null) { + return respMessage; + } + return ""; + } + }; + } + + @Override + public Future<String> futureReadString() { + return new HFuture<String>(huc) { + public boolean evalInfo(HttpURLConnection huc) throws IOException { + if (respCode == 200) { + StringBuilder sb = inputStreamToString(huc.getInputStream()); + if (sb != null) { + value = sb.toString(); + } + return true; + } + return false; + } + + @Override + public String body() { + if (value != null) { + return value; + } else if (errContent != null) { + return errContent.toString(); + } else if (respMessage != null) { + return respMessage; + } + return ""; + } + + }; + } + + @Override + public <T> Future<T> futureRead(final RosettaDF<T> df, final TYPE type) { + return new HFuture<T>(huc) { + private Data<T> data; + + public boolean evalInfo(HttpURLConnection huc) throws APIException, IOException { + if (respCode == 200) { + data = df.newData().in(type).load(huc.getInputStream()); + value = data.asObject(); + return true; + } + return false; + } + + @Override + public String body() { + if (data != null) { + try { + return data.asString(); + } catch (APIException e) { + } + } else if (errContent != null) { + return errContent.toString(); + } else if (respMessage != null) { + return respMessage; + } + return ""; + } + }; + } + + @Override + public <T> Future<T> future(final T t) { + return new HFuture<T>(huc) { + public boolean evalInfo(HttpURLConnection huc) { + if (respCode == 200) { + value = t; + return true; + } + return false; + } + + @Override + public String body() { + if (errContent != null) { + return errContent.toString(); + } else if (respMessage != null) { + return respMessage; + } + return Integer.toString(respCode); + } + }; + } + + @Override + public Future<Void> future(final HttpServletResponse resp, final int expected) throws APIException { + return new HFuture<Void>(huc) { + public boolean evalInfo(HttpURLConnection huc) throws IOException, APIException { + resp.setStatus(respCode); + int read; + InputStream is; + OutputStream os = resp.getOutputStream(); + if(respCode==expected) { + is = huc.getInputStream(); + // reuse Buffers + Pooled<byte[]> pbuff = Rcli.buffPool.get(); + try { + while((read=is.read(pbuff.content))>=0) { + os.write(pbuff.content,0,read); + } + } finally { + pbuff.done(); + } + return true; + } else { + is = huc.getErrorStream(); + if(is==null) { + is = huc.getInputStream(); + } + if(is!=null) { + errContent = new StringBuilder(); + Pooled<byte[]> pbuff = Rcli.buffPool.get(); + try { + while((read=is.read(pbuff.content))>=0) { + os.write(pbuff.content,0,read); + } + } finally { + pbuff.done(); + } + } + } + return false; + } + + @Override + public String body() { + return errContent==null?respMessage:errContent.toString(); + } + }; + } + + private static class Header { + public final String tag; + public final String value; + + public Header(String t, String v) { + this.tag = t; + this.value = v; + } + + public String toString() { + return tag + '=' + value; + } + } + + public String toString() { + return "HttpURLConnection Client configured to " + uri.toString(); + } +} diff --git a/cadi/client/src/main/java/org/onap/aaf/cadi/http/HMangr.java b/cadi/client/src/main/java/org/onap/aaf/cadi/http/HMangr.java new file mode 100644 index 00000000..2aa10ac3 --- /dev/null +++ b/cadi/client/src/main/java/org/onap/aaf/cadi/http/HMangr.java @@ -0,0 +1,248 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.cadi.http; + +import java.net.ConnectException; +import java.net.HttpURLConnection; +import java.net.SocketException; +import java.net.URI; +import java.net.URISyntaxException; + +import javax.net.ssl.SSLHandshakeException; + +import org.onap.aaf.cadi.Access; +import org.onap.aaf.cadi.CadiException; +import org.onap.aaf.cadi.Locator; +import org.onap.aaf.cadi.LocatorException; +import org.onap.aaf.cadi.SecuritySetter; +import org.onap.aaf.cadi.Access.Level; +import org.onap.aaf.cadi.Locator.Item; +import org.onap.aaf.cadi.client.Rcli; +import org.onap.aaf.cadi.client.Retryable; +import org.onap.aaf.misc.env.APIException; + +public class HMangr { + private String apiVersion; + private int readTimeout, connectionTimeout; + public final Locator<URI> loc; + private Access access; + + public HMangr(Access access, Locator<URI> loc) throws LocatorException { + readTimeout = 10000; + connectionTimeout=3000; + if(loc == null) { + throw new LocatorException("Null Locator passed"); + } + this.loc = loc; + this.access = access; + } + + /** + * Reuse the same service. This is helpful for multiple calls that change service side cached data so that + * there is not a speed issue. + * + * If the service goes down, another service will be substituted, if available. + * + * @param access + * @param loc + * @param ss + * @param item + * @param retryable + * @return + * @throws URISyntaxException + * @throws Exception + */ + public<RET> RET same(SecuritySetter<HttpURLConnection> ss, Retryable<RET> retryable) throws APIException, CadiException, LocatorException { + RET ret = null; + boolean retry = true; + Rcli<HttpURLConnection> client = retryable.lastClient(); + try { + do { + Item item; + // if no previous state, get the best + if(retryable.item()==null) { + item = loc.best(); + if(item==null) { + throw new LocatorException("No Services Found for " + loc); + } + retryable.item(item); + retryable.lastClient = null; + } + if(client==null) { + item = retryable.item(); + URI uri=loc.get(item); + if(uri==null) { + loc.invalidate(retryable.item()); + if(loc.hasItems()) { + retryable.item(loc.next(retryable.item())); + continue; + } else { + throw new LocatorException("No clients available for " + loc.toString()); + } + } + client = new HRcli(this, uri,item,ss) + .connectionTimeout(connectionTimeout) + .readTimeout(readTimeout) + .apiVersion(apiVersion); + } else { + client.setSecuritySetter(ss); + } + + retry = false; + try { + ret = retryable.code(client); + } catch (APIException | CadiException e) { + item = retryable.item(); + loc.invalidate(item); + retryable.item(loc.next(item)); + try { + Throwable ec = e.getCause(); + if(ec instanceof java.net.ConnectException) { + if(client!=null && loc.hasItems()) { + access.log(Level.WARN,"Connection refused, trying next available service"); + retry = true; + } else { + throw new CadiException("Connection refused, no more services to try"); + } + } else if(ec instanceof java.net.SocketException) { + if(client!=null && loc.hasItems()) { + access.log(Level.WARN,"Socket prematurely closed, trying next available service"); + retry = true; + } else { + throw new CadiException("Socket prematurely closed, no more services to try"); + } + } else if(ec instanceof SSLHandshakeException) { + retryable.item(null); + throw e; + } else if(ec instanceof SocketException) { + if("java.net.SocketException: Connection reset".equals(ec.getMessage())) { + access.log(Level.ERROR, ec.getMessage(), " can mean Certificate Expiration or TLS Protocol issues"); + } + retryable.item(null); + throw e; + } else { + retryable.item(null); + throw e; + } + } finally { + client = null; + } + } catch (ConnectException e) { + item = retryable.item(); + loc.invalidate(item); + retryable.item(loc.next(item)); + } + } while(retry); + } finally { + retryable.lastClient = client; + } + return ret; + } + + + public<RET> RET best(SecuritySetter<HttpURLConnection> ss, Retryable<RET> retryable) throws LocatorException, CadiException, APIException { + if(loc==null) { + throw new LocatorException("No Locator Configured"); + } + retryable.item(loc.best()); + return same(ss,retryable); + } + public<RET> RET all(SecuritySetter<HttpURLConnection> ss, Retryable<RET> retryable) throws LocatorException, CadiException, APIException { + return oneOf(ss,retryable,true,null); + } + + public<RET> RET all(SecuritySetter<HttpURLConnection> ss, Retryable<RET> retryable,boolean notify) throws LocatorException, CadiException, APIException { + return oneOf(ss,retryable,notify,null); + } + + public<RET> RET oneOf(SecuritySetter<HttpURLConnection> ss, Retryable<RET> retryable,boolean notify,String host) throws LocatorException, CadiException, APIException { + RET ret = null; + // make sure we have all current references: + loc.refresh(); + for(Item li=loc.first();li!=null;li=loc.next(li)) { + URI uri=loc.get(li); + if(host!=null && !host.equals(uri.getHost())) { + break; + } + try { + ret = retryable.code(new HRcli(this,uri,li,ss)); + access.log(Level.DEBUG,"Success calling",uri,"during call to all services"); + } catch (APIException | CadiException e) { + Throwable t = e.getCause(); + if(t!=null && t instanceof ConnectException) { + loc.invalidate(li); + access.log(Level.ERROR,"Connection to",uri,"refused during call to all services"); + } else if(t instanceof SSLHandshakeException) { + access.log(Level.ERROR,t.getMessage()); + loc.invalidate(li); + } else if(t instanceof SocketException) { + if("java.net.SocketException: Connection reset".equals(t.getMessage())) { + access.log(Level.ERROR, t.getMessage(), " can mean Certificate Expiration or TLS Protocol issues"); + } + retryable.item(null); + throw e; + } else { + throw e; + } + } catch (ConnectException e) { + loc.invalidate(li); + access.log(Level.ERROR,"Connection to",uri,"refused during call to all services"); + } + } + + if(ret == null && notify) + throw new LocatorException("No available clients to call"); + return ret; + } + + + public void close() { + // TODO Anything here? + } + + public HMangr readTimeout(int timeout) { + this.readTimeout = timeout; + return this; + } + + public int readTimeout() { + return readTimeout; + } + + public void connectionTimeout(int t) { + connectionTimeout = t; + } + + public int connectionTimout() { + return connectionTimeout; + } + + public HMangr apiVersion(String version) { + apiVersion = version; + return this; + } + + public String apiVersion() { + return apiVersion; + } + +} diff --git a/cadi/client/src/main/java/org/onap/aaf/cadi/http/HNoAuthSS.java b/cadi/client/src/main/java/org/onap/aaf/cadi/http/HNoAuthSS.java new file mode 100644 index 00000000..b857f3ad --- /dev/null +++ b/cadi/client/src/main/java/org/onap/aaf/cadi/http/HNoAuthSS.java @@ -0,0 +1,45 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.cadi.http; + +import java.io.IOException; +import java.net.HttpURLConnection; + +import javax.net.ssl.HttpsURLConnection; + +import org.onap.aaf.cadi.CadiException; +import org.onap.aaf.cadi.client.AbsAuthentication; +import org.onap.aaf.cadi.config.SecurityInfoC; + +public class HNoAuthSS extends AbsAuthentication<HttpURLConnection> { + public HNoAuthSS(SecurityInfoC<HttpURLConnection> si) throws IOException { + super(si,"noauth",null); + } + + @Override + public void setSecurity(HttpURLConnection client) throws CadiException { + if(securityInfo!=null && client instanceof HttpsURLConnection) { + securityInfo.setSocketFactoryOn((HttpsURLConnection)client); + } + } + +} diff --git a/cadi/client/src/main/java/org/onap/aaf/cadi/http/HRcli.java b/cadi/client/src/main/java/org/onap/aaf/cadi/http/HRcli.java new file mode 100644 index 00000000..f289b0e3 --- /dev/null +++ b/cadi/client/src/main/java/org/onap/aaf/cadi/http/HRcli.java @@ -0,0 +1,130 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.cadi.http; + +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URISyntaxException; + +import org.onap.aaf.cadi.CadiException; +import org.onap.aaf.cadi.LocatorException; +import org.onap.aaf.cadi.SecuritySetter; +import org.onap.aaf.cadi.Locator.Item; +import org.onap.aaf.cadi.client.EClient; +import org.onap.aaf.cadi.client.Rcli; +import org.onap.aaf.misc.env.APIException; +import org.onap.aaf.misc.env.Data.TYPE; + +/** + * Rosetta Client + * + * JAXB defined JSON or XML over HTTP/S + * + * @author Jonathan + * + * @param <T> + */ +public class HRcli extends Rcli<HttpURLConnection> { + private HMangr hman; + private Item item; + private SecuritySetter<HttpURLConnection> ss; + + public HRcli(HMangr hman, Item locItem, SecuritySetter<HttpURLConnection> secSet) throws URISyntaxException, LocatorException { + item=locItem; + uri=hman.loc.get(locItem); + this.hman = hman; + ss=secSet; + type = TYPE.JSON; + apiVersion = hman.apiVersion(); + } + + public HRcli(HMangr hman, URI uri, Item locItem, SecuritySetter<HttpURLConnection> secSet) { + locItem=item; + this.uri = uri; + this.hman = hman; + ss=secSet; + type = TYPE.JSON; + apiVersion = hman.apiVersion(); + } + + @Override + protected HRcli clone(URI uri, SecuritySetter<HttpURLConnection> ss) { + return new HRcli(hman,uri,item,ss); + } + + + + /** + * + * @return + * @throws APIException + * @throws DME2Exception + */ + protected EClient<HttpURLConnection> client() throws CadiException { + try { + if(uri==null) { + Item item = hman.loc.best(); + if(item==null) { + throw new CadiException("No service available for " + hman.loc.toString()); + } + uri = hman.loc.get(item); + } + return new HClient(ss,uri,connectionTimeout); + } catch (Exception e) { + throw new CadiException(e); + } + } + + /* (non-Javadoc) + * @see org.onap.aaf.cadi.client.Rcli#setSecuritySetter(org.onap.aaf.cadi.SecuritySetter) + */ + @Override + public void setSecuritySetter(SecuritySetter<HttpURLConnection> ss) { + this.ss = ss; + } + + /* (non-Javadoc) + * @see org.onap.aaf.cadi.client.Rcli#getSecuritySetter() + */ + @Override + public SecuritySetter<HttpURLConnection> getSecuritySetter() { + return ss; + } + + public void invalidate() throws CadiException { + try { + hman.loc.invalidate(item); + } catch (Exception e) { + throw new CadiException(e); + } + } + + public HRcli setManager(HMangr hman) { + this.hman = hman; + return this; + } + + public String toString() { + return uri.toString(); + } + +} diff --git a/cadi/client/src/main/java/org/onap/aaf/cadi/http/HTokenSS.java b/cadi/client/src/main/java/org/onap/aaf/cadi/http/HTokenSS.java new file mode 100644 index 00000000..873e0fe7 --- /dev/null +++ b/cadi/client/src/main/java/org/onap/aaf/cadi/http/HTokenSS.java @@ -0,0 +1,34 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.cadi.http; + +import java.io.IOException; +import java.net.HttpURLConnection; + +import org.onap.aaf.cadi.config.SecurityInfoC; + +public class HTokenSS extends HAuthorizationHeader { + public HTokenSS(final SecurityInfoC<HttpURLConnection> si, final String client_id, final String token) throws IOException { + super(si, client_id,"Bearer " + token); + } + +} diff --git a/cadi/client/src/main/java/org/onap/aaf/cadi/http/HTransferSS.java b/cadi/client/src/main/java/org/onap/aaf/cadi/http/HTransferSS.java new file mode 100644 index 00000000..d19c42e9 --- /dev/null +++ b/cadi/client/src/main/java/org/onap/aaf/cadi/http/HTransferSS.java @@ -0,0 +1,64 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.cadi.http; + +import java.io.IOException; +import java.net.HttpURLConnection; + +import javax.net.ssl.HttpsURLConnection; + +import org.onap.aaf.cadi.CadiException; +import org.onap.aaf.cadi.client.AbsTransferSS; +import org.onap.aaf.cadi.config.Config; +import org.onap.aaf.cadi.config.SecurityInfoC; +import org.onap.aaf.cadi.principal.TaggedPrincipal; + + +public class HTransferSS extends AbsTransferSS<HttpURLConnection> { + public HTransferSS(TaggedPrincipal principal, String app) throws IOException { + super(principal, app); + } + + public HTransferSS(TaggedPrincipal principal, String app, SecurityInfoC<HttpURLConnection> si) { + super(principal, app, si); + } + + @Override + public void setSecurity(HttpURLConnection huc) throws CadiException { + if(defSS==null) { + throw new CadiException("Need App Credentials to send message"); + } + defSS.setSecurity(huc); + if(value!=null) { + huc.addRequestProperty(Config.CADI_USER_CHAIN, value); + } + if(securityInfo!=null) { + securityInfo.setSocketFactoryOn((HttpsURLConnection)huc); + } + } + + @Override + public int setLastResponse(int respCode) { + return 0; + } + +} diff --git a/cadi/client/src/main/java/org/onap/aaf/cadi/http/HX509SS.java b/cadi/client/src/main/java/org/onap/aaf/cadi/http/HX509SS.java new file mode 100644 index 00000000..9d555f62 --- /dev/null +++ b/cadi/client/src/main/java/org/onap/aaf/cadi/http/HX509SS.java @@ -0,0 +1,152 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.cadi.http; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.security.PrivateKey; +import java.security.SecureRandom; +import java.security.Signature; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; + +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.X509KeyManager; + +import org.onap.aaf.cadi.CadiException; +import org.onap.aaf.cadi.SecuritySetter; +import org.onap.aaf.cadi.Symm; +import org.onap.aaf.cadi.client.AbsAuthentication; +import org.onap.aaf.cadi.config.Config; +import org.onap.aaf.cadi.config.SecurityInfoC; +import org.onap.aaf.misc.env.APIException; +import org.onap.aaf.misc.env.util.Chrono; + + +public class HX509SS implements SecuritySetter<HttpURLConnection> { + private static final byte[] X509 = "x509 ".getBytes(); + private PrivateKey priv; + private byte[] pub; + private String cert; + private SecurityInfoC<HttpURLConnection> securityInfo; + private String algo; + private String alias; + private static int count = new SecureRandom().nextInt(); + + public HX509SS(SecurityInfoC<HttpURLConnection> si) throws APIException, CadiException { + this(null,si,false); + } + + public HX509SS(SecurityInfoC<HttpURLConnection> si, boolean asDefault) throws APIException, CadiException { + this(null,si,asDefault); + } + + public HX509SS(final String sendAlias, SecurityInfoC<HttpURLConnection> si) throws APIException, CadiException { + this(sendAlias, si, false); + } + + public HX509SS(final String sendAlias, SecurityInfoC<HttpURLConnection> si, boolean asDefault) throws APIException, CadiException { + securityInfo = si; + if((alias=sendAlias) == null) { + if(si.default_alias == null) { + throw new APIException("JKS Alias is required to use X509SS Security. Use " + Config.CADI_ALIAS +" to set default alias"); + } else { + alias = si.default_alias; + } + } + + priv=null; + X509KeyManager[] xkms = si.getKeyManagers(); + if(xkms==null || xkms.length==0) { + throw new APIException("There are no valid keys available in given Keystores. Wrong Keypass? Expired?"); + } + for(int i=0;priv==null&&i<xkms.length;++i) { + priv = xkms[i].getPrivateKey(alias); + } + try { + for(int i=0;cert==null&&i<xkms.length;++i) { + X509Certificate[] chain = xkms[i].getCertificateChain(alias); + if(chain!=null&&chain.length>0) { + algo = chain[0].getSigAlgName(); + pub = chain[0].getEncoded(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(pub.length*2); + ByteArrayInputStream bais = new ByteArrayInputStream(pub); + Symm.base64noSplit.encode(bais,baos,X509); + cert = baos.toString(); + } + } + } catch (CertificateEncodingException | IOException e) { + throw new CadiException(e); + } + if(algo==null) { + throw new APIException("X509 Security Setter not configured"); + } + } + + @Override + public void setSecurity(HttpURLConnection huc) throws CadiException { + if(huc instanceof HttpsURLConnection) { + securityInfo.setSocketFactoryOn((HttpsURLConnection)huc); + } + if(alias==null) { // must be a one-way + huc.setRequestProperty(AbsAuthentication.AUTHORIZATION, cert); + + // Test Signed content + try { + String data = "SignedContent["+ inc() + ']' + Chrono.dateTime(); + huc.setRequestProperty("Data", data); + + Signature sig = Signature.getInstance(algo); + sig.initSign(priv); + sig.update(data.getBytes()); + byte[] signature = sig.sign(); + + ByteArrayOutputStream baos = new ByteArrayOutputStream((int)(signature.length*1.3)); + ByteArrayInputStream bais = new ByteArrayInputStream(signature); + Symm.base64noSplit.encode(bais, baos); + huc.setRequestProperty("Signature", new String(baos.toByteArray())); + + } catch (Exception e) { + throw new CadiException(e); + } + } + } + + private synchronized int inc() { + return ++count; + } + + /* (non-Javadoc) + * @see org.onap.aaf.cadi.SecuritySetter#getID() + */ + @Override + public String getID() { + return alias; + } + + @Override + public int setLastResponse(int respCode) { + return 0; + } +} diff --git a/cadi/client/src/main/java/org/onap/aaf/cadi/locator/DNSLocator.java b/cadi/client/src/main/java/org/onap/aaf/cadi/locator/DNSLocator.java new file mode 100644 index 00000000..655a0c22 --- /dev/null +++ b/cadi/client/src/main/java/org/onap/aaf/cadi/locator/DNSLocator.java @@ -0,0 +1,210 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.cadi.locator; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.URI; +import java.net.URISyntaxException; + +import org.onap.aaf.cadi.Access; +import org.onap.aaf.cadi.Locator; +import org.onap.aaf.cadi.LocatorException; +import org.onap.aaf.cadi.Access.Level; + +public class DNSLocator implements Locator<URI> { + private static enum Status {UNTRIED, OK, INVALID, SLOW}; + private static final int CHECK_TIME = 3000; + + private String host, protocol; + private Access access; + private Host[] hosts; + private int startPort, endPort; + private String suffix; + + public DNSLocator(Access access, String protocol, String host, String range) { + this.host = host; + this.protocol = protocol; + this.access = access; + int dash = range.indexOf('-'); + if(dash<0) { + startPort = endPort = Integer.parseInt(range); + } else { + startPort = Integer.parseInt(range.substring(0,dash)); + endPort = Integer.parseInt(range.substring(dash + 1)); + } + refresh(); + } + + public DNSLocator(Access access, String aaf_locate) throws LocatorException { + this.access = access; + if(aaf_locate==null) { + throw new LocatorException("Null passed into DNSLocator constructor"); + } + int start, port; + if(aaf_locate.startsWith("https:")) { + protocol = "https:"; + start = 9; // https:// + port = 443; + } else if(aaf_locate.startsWith("http:")) { + protocol = "http:"; + start = 8; // http:// + port = 80; + } else { + throw new LocatorException("DNSLocator accepts only https or http protocols. (requested URL " + aaf_locate + ')'); + } + + int colon = aaf_locate.indexOf(':',start); + int slash; + if(colon>0) { + start = colon+1; + int left = aaf_locate.indexOf('[',start); + if(left>0) { + int right = aaf_locate.indexOf(']',left+1); + if(right>0) { + int dash = aaf_locate.indexOf('-',left+1); + if(dash<0) { + startPort = endPort = Integer.parseInt(aaf_locate.substring(left+1,right)); + } else { + startPort = Integer.parseInt(aaf_locate.substring(left+1,dash)); + endPort = Integer.parseInt(aaf_locate.substring(dash + 1,right)); + } + } + + } else { + slash = aaf_locate.indexOf('/',colon+1); + if(slash<0) { + startPort = endPort = Integer.parseInt(aaf_locate.substring(start)); + } else { + startPort = endPort = Integer.parseInt(aaf_locate.substring(start,slash)); + } + } + } else { + startPort = endPort = port; + } + } + + @Override + public URI get(Item item) throws LocatorException { + return hosts[((DLItem)item).cnt].uri; + } + + @Override + public boolean hasItems() { + for(Host h : hosts) { + if(h.status==Status.OK) { + return true; + } + } + return false; + } + + @Override + public void invalidate(Item item) { + DLItem di = (DLItem)item; + hosts[di.cnt].status = Status.INVALID; + } + + @Override + public Item best() throws LocatorException { + // not a good "best" + for(int i=0;i<hosts.length;++i) { + switch(hosts[i].status) { + case OK: + return new DLItem(i); + case INVALID: + break; + case SLOW: + break; + case UNTRIED: + try { + if(hosts[i].ia.isReachable(CHECK_TIME)) { + hosts[i].status = Status.OK; + return new DLItem(i); + } + } catch (IOException e) { + throw new LocatorException(e); + } + break; + default: + break; + } + } + throw new LocatorException("No Available URIs for " + host); + } + + @Override + public Item first() throws LocatorException { + return new DLItem(0); + } + + @Override + public Item next(Item item) throws LocatorException { + DLItem di = (DLItem)item; + if(++di.cnt<hosts.length) { + return di; + } else { + return null; + } + } + + @Override + public boolean refresh() { + try { + InetAddress[] ias = InetAddress.getAllByName(host); + Host[] temp = new Host[ias.length * (1 + endPort - startPort)]; + int cnt = -1; + for(int j=startPort; j<=endPort; ++j) { + for(int i=0;i<ias.length;++i) { + temp[++cnt] = new Host(ias[i], j, suffix); + } + } + hosts = temp; + return true; + } catch (Exception e) { + access.log(Level.ERROR, e); + } + return false; + } + + private class Host { + private URI uri; + private InetAddress ia; + private Status status; + + public Host(InetAddress inetAddress, int port, String suffix) throws URISyntaxException { + ia = inetAddress; + uri = new URI(protocol,null,inetAddress.getHostAddress(),port,suffix,null,null); + status = Status.UNTRIED; + } + } + + private class DLItem implements Item { + public DLItem(int i) { + cnt = i; + } + + private int cnt; + } + + public void destroy() {} +} diff --git a/cadi/client/src/main/java/org/onap/aaf/cadi/locator/HClientHotPeerLocator.java b/cadi/client/src/main/java/org/onap/aaf/cadi/locator/HClientHotPeerLocator.java new file mode 100644 index 00000000..b97768a6 --- /dev/null +++ b/cadi/client/src/main/java/org/onap/aaf/cadi/locator/HClientHotPeerLocator.java @@ -0,0 +1,60 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.cadi.locator; + +import java.net.URI; +import java.net.URISyntaxException; + +import org.onap.aaf.cadi.Access; +import org.onap.aaf.cadi.LocatorException; +import org.onap.aaf.cadi.http.HClient; +import org.onap.aaf.cadi.http.HX509SS; + +public class HClientHotPeerLocator extends HotPeerLocator<HClient> { + private final HX509SS ss; + + public HClientHotPeerLocator(Access access, String urlstr, long invalidateTime, String localLatitude, + String localLongitude, HX509SS ss) throws LocatorException { + super(access, urlstr, invalidateTime, localLatitude, localLongitude); + + this.ss = ss; + } + + @Override + protected HClient _newClient(String clientInfo) throws LocatorException { + try { + int idx = clientInfo.indexOf('/'); + return new HClient(ss,new URI("https://"+(idx<0?clientInfo:clientInfo.substring(0, idx))),3000); + } catch (URISyntaxException e) { + throw new LocatorException(e); + } + } + + @Override + protected HClient _invalidate(HClient client) { + return null; + } + + @Override + protected void _destroy(HClient client) { + } +}
\ No newline at end of file diff --git a/cadi/client/src/main/java/org/onap/aaf/cadi/locator/HotPeerLocator.java b/cadi/client/src/main/java/org/onap/aaf/cadi/locator/HotPeerLocator.java new file mode 100644 index 00000000..17f9bafb --- /dev/null +++ b/cadi/client/src/main/java/org/onap/aaf/cadi/locator/HotPeerLocator.java @@ -0,0 +1,302 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.cadi.locator; + +import org.onap.aaf.cadi.Access; +import org.onap.aaf.cadi.Locator; +import org.onap.aaf.cadi.LocatorException; +import org.onap.aaf.cadi.Access.Level; +import org.onap.aaf.cadi.routing.GreatCircle; +import org.onap.aaf.misc.env.util.Split; + +/** + * This Locator is to handle Hot Peer load protection, when the Servers are + * 1) Static + * 2) Well known client URL + * + * The intention is to change traffic over to the Hot Peer, if a server goes down, and reinstate + * when it is back up. + * + * Example of this kind of Service is a MS Certificate Server + * + * @author Jonathan + * + * @param <CLIENT> + */ +public abstract class HotPeerLocator<CLIENT> implements Locator<CLIENT> { + private final String[] urlstrs; + private final CLIENT[] clients; + private final long[] failures; + private final double[] distances; + private int preferred; + private long invalidateTime; + private Thread refreshThread; + protected Access access; + + /** + * Construct: Expect one or more Strings in the form: + * 192.555.112.223:39/38.88087/-77.30122 + * separated by commas + * + * @param trans + * @param urlstr + * @param invalidateTime + * @param localLatitude + * @param localLongitude + * @throws LocatorException + */ + @SuppressWarnings("unchecked") + protected HotPeerLocator(Access access, final String urlstr, final long invalidateTime, final String localLatitude, final String localLongitude) throws LocatorException { + this.access = access; + urlstrs = Split.split(',', urlstr); + clients = (CLIENT[])new Object[urlstrs.length]; + failures = new long[urlstrs.length]; + distances= new double[urlstrs.length]; + this.invalidateTime = invalidateTime; + + double distance = Double.MAX_VALUE; + for(int i=0;i<urlstrs.length;++i) { + String[] info = Split.split('/', urlstrs[i]); + if(info.length<3) { + throw new LocatorException("Configuration needs LAT and LONG, i.e. ip:port/lat/long"); + } + try { + clients[i] = _newClient(urlstrs[i]); + failures[i] = 0L; + } catch(LocatorException le) { + failures[i] = System.currentTimeMillis()+invalidateTime; + } + + double d = GreatCircle.calc(info[1],info[2],localLatitude,localLongitude); + distances[i]=d; + + // find preferred server + if(d<distance) { + preferred = i; + distance=d; + } + } + + access.printf(Level.INIT,"Preferred Client is %s",urlstrs[preferred]); + for(int i=0;i<urlstrs.length;++i) { + if(i!=preferred) { + access.printf(Level.INIT,"Alternate Client is %s",urlstrs[i]); + } + } + } + + protected abstract CLIENT _newClient(String hostInfo) throws LocatorException; + /** + * If client can reconnect, then return. Otherwise, destroy and return null; + * @param client + * @return + * @throws LocatorException + */ + protected abstract CLIENT _invalidate(CLIENT client); + + protected abstract void _destroy(CLIENT client); + + @Override + public Item best() throws LocatorException { + if(failures[preferred]==0L) { + return new HPItem(preferred); + } else { + long now = System.currentTimeMillis(); + double d = Double.MAX_VALUE; + int best = -1; + boolean tickle = false; + // try for best existing client + for(int i=0;i<urlstrs.length;++i) { + if(failures[i]<now && distances[i]<d) { + if(clients[i]!=null) { + best = i; + break; + } else { + tickle = true; // There's some failed clients which can be restored + } + } + } + if(best<0 && tickle) { + tickle=false; + if(refresh()) { + // try again + for(int i=0;i<urlstrs.length;++i) { + if(failures[i]==0L && distances[i]<d) { + if(clients[i]!=null) { + best = i; + break; + } + } + } + } + } + + /* + * If a valid client is available, but there are some that can refresh, return the client immediately + * but start a Thread to do the background Client setup. + */ + if(tickle) { + synchronized(clients) { + if(refreshThread==null) { + refreshThread = new Thread(new Runnable(){ + @Override + public void run() { + refresh(); + refreshThread = null; + } + }); + refreshThread.setDaemon(true); + refreshThread.start(); + } + } + } + + if(best<0) { + throw new LocatorException("No Clients available"); + } + + + return new HPItem(best); + } + } + + + @Override + public CLIENT get(Item item) throws LocatorException { + HPItem hpi = (HPItem)item; + CLIENT c = clients[hpi.idx]; + if(c==null) { + if(failures[hpi.idx]>System.currentTimeMillis()) { + throw new LocatorException("Client requested is invalid"); + } else { + synchronized(clients) { + c = _newClient(urlstrs[hpi.idx]); + failures[hpi.idx]=0L; + } + } + } else if(failures[hpi.idx]>0){ + throw new LocatorException("Client requested is invalid"); + } + return c; + } + + public String info(Item item) { + HPItem hpi = (HPItem)item; + if(hpi!=null && hpi.idx<urlstrs.length) { + return urlstrs[hpi.idx]; + } else { + return "Invalid Item"; + } + } + + @Override + public boolean hasItems() { + for(int i=0;i<clients.length;++i) { + if(clients[i]!=null && failures[i]==0L) { + return true; + } + } + return false; + } + + @Override + public synchronized void invalidate(Item item) throws LocatorException { + HPItem hpi = (HPItem)item; + failures[hpi.idx] = System.currentTimeMillis() + invalidateTime; + CLIENT c = clients[hpi.idx]; + clients[hpi.idx] = _invalidate(c); + } + + @Override + public Item first() throws LocatorException { + return new HPItem(0); + } + + @Override + public Item next(Item item) throws LocatorException { + HPItem hpi = (HPItem)item; + if(++hpi.idx>=clients.length) { + return null; + } + return hpi; + } + + @Override + public boolean refresh() { + boolean force = !hasItems(); // If no Items at all, reset + boolean rv = true; + long now = System.currentTimeMillis(); + for(int i=0;i<clients.length;++i) { + if(failures[i]>0L && (failures[i]<now || force)) { // retry + try { + synchronized(clients) { + if(clients[i]==null) { + clients[i]=_newClient(urlstrs[i]); + } + failures[i]=0L; + } + } catch (LocatorException e) { + failures[i]=now+invalidateTime; + rv = false; + } + } + } + return rv; + } + + @Override + public void destroy() { + for(int i=0;i<clients.length;++i) { + if(clients[i]!=null) { + _destroy(clients[i]); + clients[i] = null; + } + } + } + + private static class HPItem implements Item { + private int idx; + + public HPItem(int i) { + idx = i; + } + } + + + /* + * Convenience Functions + */ + public CLIENT bestClient() throws LocatorException { + return get(best()); + } + + public boolean invalidate(CLIENT client) throws LocatorException { + for(int i=0;i<clients.length;++i) { + if(clients[i]==client) { // yes, "==" is appropriate here.. Comparing Java Object Reference + invalidate(new HPItem(i)); + return true; + } + } + return false; + } + + }
\ No newline at end of file diff --git a/cadi/client/src/main/java/org/onap/aaf/cadi/locator/PropertyLocator.java b/cadi/client/src/main/java/org/onap/aaf/cadi/locator/PropertyLocator.java new file mode 100644 index 00000000..244a43bd --- /dev/null +++ b/cadi/client/src/main/java/org/onap/aaf/cadi/locator/PropertyLocator.java @@ -0,0 +1,289 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.cadi.locator; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.UnknownHostException; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; + +import org.onap.aaf.cadi.Locator; +import org.onap.aaf.cadi.LocatorException; +import org.onap.aaf.misc.env.util.Split; + +public class PropertyLocator implements Locator<URI> { + private final URI [] orig; + private PLItem[] current; + private int end; + private final SecureRandom random; + private URI[] resolved; + private long lastRefreshed; + private long minRefresh; + private long backgroundRefresh; + + public PropertyLocator(String locList) throws LocatorException { + this(locList,10000L, 1000*60*20); // defaults, do not refresh more than once in 10 seconds, Refresh Locator every 20 mins. + } + /** + * comma delimited root url list + * + * @param locList + * @throws LocatorException + */ + public PropertyLocator(String locList, long minRefreshMillis, long backgroundRefreshMillis) throws LocatorException { + minRefresh = minRefreshMillis; + backgroundRefresh = backgroundRefreshMillis; + lastRefreshed=0L; + if(locList==null) { + throw new LocatorException("No Location List given for PropertyLocator"); + } + String[] locarray = Split.split(',',locList); + List<URI> uriList = new ArrayList<URI>(); + + random = new SecureRandom(); + + for(int i=0;i<locarray.length;++i) { + try { + int range = locarray[i].indexOf(":["); + if(range<0) { + uriList.add(new URI(locarray[i])); + } else { + String mach_colon = locarray[i].substring(0, range+1); + int dash = locarray[i].indexOf('-',range+2); + int brac = locarray[i].indexOf(']',dash+1); + int slash = locarray[i].indexOf('/',brac); + int start = Integer.parseInt(locarray[i].substring(range+2, dash)); + int end = Integer.parseInt(locarray[i].substring(dash+1, brac)); + for(int port=start;port<=end;++port) { + uriList.add(new URI(mach_colon+port + (slash>=0?locarray[i].substring(slash):""))); + } + } + } catch (NumberFormatException nf) { + throw new LocatorException("Invalid URI format: " + locarray[i]); + } catch (URISyntaxException e) { + throw new LocatorException(e); + } + } + orig = new URI[uriList.size()]; + uriList.toArray(orig); + + refresh(); + new Timer("PropertyLocator Refresh Timer",true).scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + refresh(); + } + }, backgroundRefresh,backgroundRefresh); + } + + @Override + public URI get(Item item) throws LocatorException { + synchronized(orig) { + if(item==null) { + return null; + } else { + return resolved[((PLItem)item).idx]; + } + } + } + + @Override + public Item first() throws LocatorException { + return end>0?current[0]:null; + } + + @Override + public boolean hasItems() { + return end>0; + } + + @Override + public Item next(Item item) throws LocatorException { + if(item==null) { + return null; + } else { + int spot; + if((spot=(((PLItem)item).order+1))>=end)return null; + return current[spot]; + } + } + + @Override + public synchronized void invalidate(Item item) throws LocatorException { + if(--end<=0) { + refresh(); + return; + } + if(item==null) { + return; + } + PLItem pli = (PLItem)item; + int i,order; + for(i=0;i<end;++i) { + if(pli==current[i])break; + } + order = current[i].order; + for(;i<end;++i) { + current[i]=current[i+1]; + current[i].order=order++; + } + current[end]=pli; + } + + @Override + public Item best() throws LocatorException { + if(current.length==0) { + refresh(); + } + switch(current.length) { + case 0: + return null; + case 1: + return current[0]; + default: + return current[Math.abs(random.nextInt())%current.length]; + } + } + + @Override + public synchronized boolean refresh() { + if(System.currentTimeMillis()>lastRefreshed) { + // Build up list + List<URI> resolve = new ArrayList<URI>(); + String realname; + for(int i = 0; i < orig.length ; ++i) { + try { + InetAddress ia[] = InetAddress.getAllByName(orig[i].getHost()); + + URI o,n; + for(int j=0;j<ia.length;++j) { + o = orig[i]; + Socket socket = new Socket(); + try { + realname=ia[j].getHostAddress().equals(ia[j].getHostName())?ia[j].getCanonicalHostName():ia[j].getHostName(); + int port = o.getPort(); + if(port<0) { // default + port = "https".equalsIgnoreCase(o.getScheme())?443:80; + } + socket.connect(new InetSocketAddress(realname,port),3000); + if(socket.isConnected()) { + n = new URI( + o.getScheme(), + o.getUserInfo(), + realname, + o.getPort(), + o.getPath(), + o.getQuery(), + o.getFragment() + ); + resolve.add(n); + } + } catch (IOException e) { + } finally { + if(!socket.isClosed()) { + try { + socket.close(); + } catch (IOException e) { + // nothing to do. + } + } + } + } + } catch (UnknownHostException | URISyntaxException e) { + // Note: Orig Name already known as valid, based on constructor + } + } + end=resolve.size(); + PLItem[] newCurrent; + if(current==null || current.length!=end) { + newCurrent = new PLItem[end]; + } else { + newCurrent = current; + } + + for(int i=0; i< end; ++i) { + if(newCurrent[i]==null){ + newCurrent[i]=new PLItem(i); + } else { + newCurrent[i].idx=newCurrent[i].order=i; + } + } + synchronized(orig) { + resolved = new URI[end]; + resolve.toArray(resolved); + current = newCurrent; + } + lastRefreshed = System.currentTimeMillis()+minRefresh; + return !resolve.isEmpty(); + } else { + return false; + } + } + + private class PLItem implements Item { + public int idx,order; + + public PLItem(int i) { + idx = order =i; + } + + public String toString() { + return "Item: " + idx + " order: " + order; + } + } + + public String toString() { + StringBuilder sb = new StringBuilder(); + boolean first = true; + for(URI uri : orig) { + boolean isResolved=false; + if(uri!=null) { + if(first) { + first = false; + } else { + sb.append(", "); + } + sb.append(uri.toString()); + sb.append(" ["); + for(URI u2 : resolved) { + if(uri.equals(u2)) { + isResolved = true; + break; + } + } + sb.append(isResolved?"X]\n":" ]"); + } + } + return sb.toString(); + } + + public void destroy() { + } +} diff --git a/cadi/client/src/main/java/org/onap/aaf/cadi/routing/GreatCircle.java b/cadi/client/src/main/java/org/onap/aaf/cadi/routing/GreatCircle.java new file mode 100644 index 00000000..36906188 --- /dev/null +++ b/cadi/client/src/main/java/org/onap/aaf/cadi/routing/GreatCircle.java @@ -0,0 +1,188 @@ +/** + * ============LICENSE_START==================================================== + * org.onap.aaf + * =========================================================================== + * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. + * =========================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END==================================================== + * + */ + +package org.onap.aaf.cadi.routing; + +import org.onap.aaf.misc.env.util.Split; + +public class GreatCircle { + // Note: multiplying by this constant is faster than calling Math equivalent function + private static final double DEGREES_2_RADIANS = Math.PI/180.0; + + public static final double DEGREES_2_NM = 60; + public static final double DEGREES_2_KM = DEGREES_2_NM * 1.852; // 1.852 is exact ratio per 1929 Standard Treaty, adopted US 1954 + public static final double DEGREES_2_MI = DEGREES_2_NM * 1.1507795; + + /** + * + * Calculate the length of an arc on a perfect sphere based on Latitude and Longitudes of two points + * Parameters are in Degrees (i.e. the coordinate system you get from GPS, Mapping WebSites, Phones, etc) + * + * L1 = Latitude of point A + * G1 = Longitude of point A + * L2 = Latitude of point B + * G2 = Longitude of point B + * + * d = acos (sin(L1)*sin(L2) + cos(L1)*cos(L2)*cos(G1 - G2)) + * + * Returns answer in Degrees + * + * Since there are 60 degrees per nautical miles, you can convert to NM by multiplying by 60 + * + * Essential formula from a Princeton website, the "Law of Cosines" method. + * + * Refactored cleaned up for speed Jonathan 3/8/2013 + * + * @param latA + * @param lonA + * @param latB + * @param lonB + * @return + */ + public static double calc(double latA, double lonA, double latB, double lonB) { + // Formula requires Radians. Expect Params to be Coordinates (Degrees) + // Simple ratio, quicker than calling Math.toRadians() + latA *= DEGREES_2_RADIANS; + lonA *= DEGREES_2_RADIANS; + latB *= DEGREES_2_RADIANS; + lonB *= DEGREES_2_RADIANS; + + return Math.acos( + Math.sin(latA) * Math.sin(latB) + + Math.cos(latA) * Math.cos(latB) * Math.cos(lonA-lonB) + ) + / DEGREES_2_RADIANS; + } + + /** + * Convert from "Lat,Long Lat,Long" String format + * "Lat,Long,Lat,Long" Format + * or all four entries "Lat Long Lat Long" + * + * (Convenience function) + * + * Since Distance is positive, a "-1" indicates an error in String formatting + */ + public static double calc(String ... coords) { + try { + String [] array; + switch(coords.length) { + case 1: + array = Split.split(',',coords[0]); + if(array.length!=4)return -1; + return calc( + Double.parseDouble(array[0]), + Double.parseDouble(array[1]), + Double.parseDouble(array[2]), + Double.parseDouble(array[3]) + ); + case 2: + array = Split.split(',',coords[0]); + String [] array2 = Split.split(',',coords[1]); + if(array.length!=2 || array2.length!=2)return -1; + return calc( + Double.parseDouble(array[0]), + Double.parseDouble(array[1]), + Double.parseDouble(array2[0]), + Double.parseDouble(array2[1]) + ); + case 4: + return calc( + Double.parseDouble(coords[0]), + Double.parseDouble(coords[1]), + Double.parseDouble(coords[2]), + Double.parseDouble(coords[3]) + ); + + default: + return -1; + } + } catch (NumberFormatException e) { + return -1; + } + } + +} + +///** +//* Haverside method, from Princeton +//* +//* @param alat +//* @param alon +//* @param blat +//* @param blon +//* @return +//*/ +//public static double calc3(double alat, double alon, double blat, double blon) { +// alat *= DEGREES_2_RADIANS; +// alon *= DEGREES_2_RADIANS; +// blat *= DEGREES_2_RADIANS; +// blon *= DEGREES_2_RADIANS; +// return 2 * Math.asin( +// Math.min(1, Math.sqrt( +// Math.pow(Math.sin((blat-alat)/2), 2) + +// (Math.cos(alat)*Math.cos(blat)* +// Math.pow( +// Math.sin((blon-alon)/2),2) +// ) +// ) +// ) +// ) +// / DEGREES_2_RADIANS; +//} +// + + + +//This is a MEAN radius. The Earth is not perfectly spherical +// public static final double EARTH_RADIUS_KM = 6371.0; +// public static final double EARTH_RADIUS_NM = 3440.07; +// public static final double KM_2_MILES_RATIO = 0.621371192; +///** +//* Code on Internet based on Unknown book. Lat/Long is in Degrees +//* @param alat +//* @param alon +//* @param blat +//* @param blon +//* @return +//*/ +//public static double calc1(double alat, double alon, double blat, double blon) { +// alat *= DEGREES_2_RADIANS; +// alon *= DEGREES_2_RADIANS; +// blat *= DEGREES_2_RADIANS; +// blon *= DEGREES_2_RADIANS; +// +// // Reused values +// double cosAlat,cosBlat; +// +// return Math.acos( +// ((cosAlat=Math.cos(alat))*Math.cos(alon)*(cosBlat=Math.cos(blat))*Math.cos(blon)) + +// (cosAlat*Math.sin(alon)*cosBlat*Math.sin(blon)) + +// (Math.sin(alat)*Math.sin(blat)) +// )/DEGREES_2_RADIANS; +// +//} + +/* +* This method was 50% faster than calculation 1, and 75% than the Haverside method +* Also, since it's based off of Agree standard Degrees of the Earth, etc, the calculations are more exact, +* at least for Nautical Miles and Kilometers +*/ |