001/*
002 * (C) Copyright 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     bstefanescu
018 *     ataillefer
019 */
020package org.nuxeo.ecm.automation.client.jaxrs.spi;
021
022import static org.nuxeo.ecm.automation.client.Constants.CTYPE_AUTOMATION;
023import static org.nuxeo.ecm.automation.client.Constants.CTYPE_ENTITY;
024
025import java.io.IOException;
026import java.util.function.Supplier;
027
028import org.nuxeo.ecm.automation.client.AdapterFactory;
029import org.nuxeo.ecm.automation.client.AdapterManager;
030import org.nuxeo.ecm.automation.client.AutomationClient;
031import org.nuxeo.ecm.automation.client.LoginCallback;
032import org.nuxeo.ecm.automation.client.LoginInfo;
033import org.nuxeo.ecm.automation.client.Session;
034import org.nuxeo.ecm.automation.client.TokenCallback;
035import org.nuxeo.ecm.automation.client.jaxrs.spi.auth.BasicAuthInterceptor;
036import org.nuxeo.ecm.automation.client.jaxrs.spi.auth.TokenAuthInterceptor;
037import org.nuxeo.ecm.automation.client.jaxrs.spi.marshallers.PojoMarshaller;
038import org.nuxeo.ecm.automation.client.model.OperationRegistry;
039
040/**
041 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
042 * @author <a href="mailto:ataillefer@nuxeo.com">Antoine Taillefer</a>
043 */
044public abstract class AbstractAutomationClient implements AutomationClient {
045
046    private static final Object SHARED_REGISTRY_SYNCHRONIZER = new Object();
047
048    // Use an operation registry shared by the multiple client sessions for
049    // performance reasons
050    private static volatile OperationRegistry sharedRegistry;
051
052    private static long sharedRegistryUpdateTimestamp = 0L;
053
054    private static long sharedRegistryExpirationDelay = 60000L;
055
056    protected final AdapterManager adapterManager = new AdapterManager();
057
058    protected String url;
059
060    protected Supplier<String> urlSupplier;
061
062    protected volatile OperationRegistry registry;
063
064    protected RequestInterceptor requestInterceptor;
065
066    protected AbstractAutomationClient(String url) {
067        this.url = url.endsWith("/") ? url : url + "/";
068    }
069
070    /** @since 10.10 */
071    protected AbstractAutomationClient(Supplier<String> urlSupplier) {
072        this.urlSupplier = urlSupplier;
073    }
074
075    /**
076     * Gets access to this request interceptor
077     */
078    public RequestInterceptor getRequestInterceptor() {
079        return requestInterceptor;
080    }
081
082    /**
083     * Can be used for intercepting requests before they are being sent to the server.
084     */
085    @Override
086    public void setRequestInterceptor(RequestInterceptor interceptor) {
087        requestInterceptor = interceptor;
088    }
089
090    @Override
091    public String getBaseUrl() {
092        return urlSupplier == null ? url : urlSupplier.get();
093    }
094
095    public void setBasicAuth(String username, String password) {
096        setRequestInterceptor(new BasicAuthInterceptor(username, password));
097    }
098
099    protected OperationRegistry getRegistry() {
100        return registry;
101    }
102
103    @Override
104    public void registerAdapter(AdapterFactory<?> factory) {
105        adapterManager.registerAdapter(factory);
106    }
107
108    @SuppressWarnings("unchecked")
109    @Override
110    public <T> T getAdapter(Session session, Class<T> adapterType) {
111        return adapterManager.getAdapter(session, adapterType);
112    }
113
114    protected OperationRegistry connect(Connector connector) throws IOException {
115        Request req = new Request(Request.GET, getBaseUrl());
116        req.put("Accept", CTYPE_AUTOMATION);
117        // TODO handle authorization failure
118        return (OperationRegistry) connector.execute(req);
119    }
120
121    public synchronized void shutdown() {
122        adapterManager.clear();
123        url = null;
124        urlSupplier = null;
125        registry = null;
126    }
127
128    public Session getSession() throws IOException {
129        Connector connector = newConnector();
130        if (requestInterceptor != null) {
131            connector = new ConnectorHandler(connector, requestInterceptor);
132        }
133        if (registry == null) { // not yet connected
134            if (System.currentTimeMillis() - sharedRegistryUpdateTimestamp < sharedRegistryExpirationDelay) {
135                registry = sharedRegistry;
136            } else {
137                synchronized (SHARED_REGISTRY_SYNCHRONIZER) {
138                    // duplicate the test to avoid reentrance
139                    if (System.currentTimeMillis() - sharedRegistryUpdateTimestamp < sharedRegistryExpirationDelay) {
140                        registry = sharedRegistry;
141                    } else {
142                        // retrieve the registry
143                        registry = connect(connector);
144                        sharedRegistry = registry;
145                        sharedRegistryUpdateTimestamp = System.currentTimeMillis();
146                    }
147                }
148            }
149        }
150        return login(connector);
151    }
152
153    public Session getSession(final String username, final String password) throws IOException {
154        return getSession(new BasicAuthInterceptor(username, password));
155    }
156
157    public Session getSession(final String token) throws IOException {
158        return getSession(new TokenAuthInterceptor(token));
159    }
160
161    protected Session getSession(RequestInterceptor interceptor) throws IOException {
162        setRequestInterceptor(interceptor);
163        return getSession();
164    }
165
166    public Session getSession(TokenCallback cb) throws IOException {
167        String token = cb.getLocalToken();
168        if (token == null) {
169            token = cb.getRemoteToken(cb.getTokenParams());
170            cb.saveToken(token);
171        }
172        return getSession(token);
173    }
174
175    @Override
176    public Session getSession(LoginCallback loginCb) {
177        throw new UnsupportedOperationException();
178    }
179
180    protected Session login(Connector connector) throws IOException {
181        Request request = new Request(Request.POST, getBaseUrl() + getRegistry().getPath("login"));
182        request.put("Accept", CTYPE_ENTITY);
183        LoginInfo login = (LoginInfo) connector.execute(request);
184        return createSession(connector, login);
185    }
186
187    protected Session createSession(final Connector connector, final LoginInfo login) {
188        return new DefaultSession(this, connector, login == null ? LoginInfo.ANONYNMOUS : login);
189    }
190
191    public void asyncExec(Runnable runnable) {
192        throw new UnsupportedOperationException("Async execution not supported");
193    }
194
195    protected abstract Connector newConnector();
196
197    /**
198     * @since 5.7
199     */
200    public void setSharedRegistryExpirationDelay(long delay) {
201        sharedRegistryExpirationDelay = delay;
202    }
203
204    @Override
205    public void registerPojoMarshaller(Class clazz) {
206        JsonMarshalling.addMarshaller(PojoMarshaller.forClass(clazz));
207    }
208}