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;
026
027import org.nuxeo.ecm.automation.client.AdapterFactory;
028import org.nuxeo.ecm.automation.client.AdapterManager;
029import org.nuxeo.ecm.automation.client.AutomationClient;
030import org.nuxeo.ecm.automation.client.LoginCallback;
031import org.nuxeo.ecm.automation.client.LoginInfo;
032import org.nuxeo.ecm.automation.client.Session;
033import org.nuxeo.ecm.automation.client.TokenCallback;
034import org.nuxeo.ecm.automation.client.jaxrs.spi.auth.BasicAuthInterceptor;
035import org.nuxeo.ecm.automation.client.jaxrs.spi.auth.TokenAuthInterceptor;
036import org.nuxeo.ecm.automation.client.jaxrs.spi.marshallers.PojoMarshaller;
037import org.nuxeo.ecm.automation.client.model.OperationRegistry;
038
039/**
040 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
041 * @author <a href="mailto:ataillefer@nuxeo.com">Antoine Taillefer</a>
042 */
043public abstract class AbstractAutomationClient implements AutomationClient {
044
045    private static final Object SHARED_REGISTRY_SYNCHRONIZER = new Object();
046
047    // Use an operation registry shared by the multiple client sessions for
048    // performance reasons
049    private static volatile OperationRegistry sharedRegistry;
050
051    private static long sharedRegistryUpdateTimestamp = 0L;
052
053    private static long sharedRegistryExpirationDelay = 60000L;
054
055    protected final AdapterManager adapterManager = new AdapterManager();
056
057    protected String url;
058
059    protected volatile OperationRegistry registry;
060
061    protected RequestInterceptor requestInterceptor;
062
063    protected AbstractAutomationClient(String url) {
064        this.url = url.endsWith("/") ? url : url + "/";
065    }
066
067    /**
068     * Gets access to this request interceptor
069     */
070    public RequestInterceptor getRequestInterceptor() {
071        return requestInterceptor;
072    }
073
074    /**
075     * Can be used for intercepting requests before they are being sent to the server.
076     */
077    @Override
078    public void setRequestInterceptor(RequestInterceptor interceptor) {
079        requestInterceptor = interceptor;
080    }
081
082    @Override
083    public String getBaseUrl() {
084        return url;
085    }
086
087    public void setBasicAuth(String username, String password) {
088        setRequestInterceptor(new BasicAuthInterceptor(username, password));
089    }
090
091    protected OperationRegistry getRegistry() {
092        return registry;
093    }
094
095    @Override
096    public void registerAdapter(AdapterFactory<?> factory) {
097        adapterManager.registerAdapter(factory);
098    }
099
100    @SuppressWarnings("unchecked")
101    @Override
102    public <T> T getAdapter(Session session, Class<T> adapterType) {
103        return adapterManager.getAdapter(session, adapterType);
104    }
105
106    protected OperationRegistry connect(Connector connector) throws IOException {
107        Request req = new Request(Request.GET, url);
108        req.put("Accept", CTYPE_AUTOMATION);
109        // TODO handle authorization failure
110        return (OperationRegistry) connector.execute(req);
111    }
112
113    public synchronized void shutdown() {
114        adapterManager.clear();
115        url = null;
116        registry = null;
117    }
118
119    public Session getSession() throws IOException {
120        Connector connector = newConnector();
121        if (requestInterceptor != null) {
122            connector = new ConnectorHandler(connector, requestInterceptor);
123        }
124        if (registry == null) { // not yet connected
125            if (System.currentTimeMillis() - sharedRegistryUpdateTimestamp < sharedRegistryExpirationDelay) {
126                registry = sharedRegistry;
127            } else {
128                synchronized (SHARED_REGISTRY_SYNCHRONIZER) {
129                    // duplicate the test to avoid reentrance
130                    if (System.currentTimeMillis() - sharedRegistryUpdateTimestamp < sharedRegistryExpirationDelay) {
131                        registry = sharedRegistry;
132                    } else {
133                        // retrieve the registry
134                        registry = connect(connector);
135                        sharedRegistry = registry;
136                        sharedRegistryUpdateTimestamp = System.currentTimeMillis();
137                    }
138                }
139            }
140        }
141        return login(connector);
142    }
143
144    public Session getSession(final String username, final String password) throws IOException {
145        return getSession(new BasicAuthInterceptor(username, password));
146    }
147
148    public Session getSession(final String token) throws IOException {
149        return getSession(new TokenAuthInterceptor(token));
150    }
151
152    protected Session getSession(RequestInterceptor interceptor) throws IOException {
153        setRequestInterceptor(interceptor);
154        return getSession();
155    }
156
157    public Session getSession(TokenCallback cb) throws IOException {
158        String token = cb.getLocalToken();
159        if (token == null) {
160            token = cb.getRemoteToken(cb.getTokenParams());
161            cb.saveToken(token);
162        }
163        return getSession(token);
164    }
165
166    @Override
167    public Session getSession(LoginCallback loginCb) {
168        throw new UnsupportedOperationException();
169    }
170
171    protected Session login(Connector connector) throws IOException {
172        Request request = new Request(Request.POST, url + getRegistry().getPath("login"));
173        request.put("Accept", CTYPE_ENTITY);
174        LoginInfo login = (LoginInfo) connector.execute(request);
175        return createSession(connector, login);
176    }
177
178    protected Session createSession(final Connector connector, final LoginInfo login) {
179        return new DefaultSession(this, connector, login == null ? LoginInfo.ANONYNMOUS : login);
180    }
181
182    public void asyncExec(Runnable runnable) {
183        throw new UnsupportedOperationException("Async execution not supported");
184    }
185
186    protected abstract Connector newConnector();
187
188    /**
189     * @since 5.7
190     */
191    public void setSharedRegistryExpirationDelay(long delay) {
192        sharedRegistryExpirationDelay = delay;
193    }
194
195    @Override
196    public void registerPojoMarshaller(Class clazz) {
197        JsonMarshalling.addMarshaller(PojoMarshaller.forClass(clazz));
198    }
199}