001/**
002 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
003 *   This file is part of the LDP4j Project:
004 *     http://www.ldp4j.org/
005 *
006 *   Center for Open Middleware
007 *     http://www.centeropenmiddleware.com/
008 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
009 *   Copyright (C) 2014 Center for Open Middleware.
010 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
011 *   Licensed under the Apache License, Version 2.0 (the "License");
012 *   you may not use this file except in compliance with the License.
013 *   You may obtain a copy of the License at
014 *
015 *             http://www.apache.org/licenses/LICENSE-2.0
016 *
017 *   Unless required by applicable law or agreed to in writing, software
018 *   distributed under the License is distributed on an "AS IS" BASIS,
019 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
020 *   See the License for the specific language governing permissions and
021 *   limitations under the License.
022 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
023 *   Artifact    : org.ldp4j.framework:ldp4j-application-api:0.1.0
024 *   Bundle      : ldp4j-application-api-0.1.0.jar
025 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
026 */
027package org.ldp4j.application;
028
029import org.ldp4j.application.data.Individual;
030import org.ldp4j.application.data.Name;
031import org.ldp4j.application.ext.ResourceHandler;
032import org.ldp4j.application.session.ResourceSnapshot;
033import org.ldp4j.application.session.WriteSession;
034import org.ldp4j.application.session.WriteSessionException;
035import org.ldp4j.application.spi.RuntimeDelegate;
036import org.slf4j.Logger;
037import org.slf4j.LoggerFactory;
038
039import static com.google.common.base.Preconditions.*;
040
041
042/**
043 * A proxy class that acts as a facade for interacting with the Application
044 * Engine proactively.
045 */
046public final class ApplicationContext {
047
048  private static final class ApplicationEngineSingleton {
049
050    private static final ApplicationContext SINGLETON=new ApplicationContext();
051
052    private ApplicationEngineSingleton() {
053    }
054
055  }
056
057  private final class SafeWriteSession implements WriteSession {
058
059    private final WriteSession nativeSession;
060
061    private boolean dispossed;
062    private boolean completed;
063
064    private SafeWriteSession(WriteSession session) {
065      this.nativeSession = session;
066    }
067
068    private void verifyExecutability() {
069      checkState(!this.dispossed,"Session has already been dispossed");
070      checkState(!this.completed,"Session has already been completed");
071    }
072
073    @Override
074    public <S extends ResourceSnapshot> S resolve(Class<? extends S> snapshotClass, Individual<?, ?> individual) {
075      verifyExecutability();
076      return this.nativeSession.resolve(snapshotClass,individual);
077    }
078
079    @Override
080    public <S extends ResourceSnapshot> S find(Class<? extends S> snapshotClass, Name<?> id, Class<? extends ResourceHandler> handlerClass) {
081      verifyExecutability();
082      return this.nativeSession.find(snapshotClass,id,handlerClass);
083    }
084
085    @Override
086    public void modify(ResourceSnapshot resource) {
087      verifyExecutability();
088      this.nativeSession.modify(resource);
089    }
090
091    @Override
092    public void delete(ResourceSnapshot resource) {
093      verifyExecutability();
094      this.nativeSession.delete(resource);
095    }
096
097    @Override
098    public void saveChanges() throws WriteSessionException {
099      verifyExecutability();
100      this.completed=true;
101      this.nativeSession.saveChanges();
102    }
103
104    @Override
105    public void discardChanges() throws WriteSessionException {
106      verifyExecutability();
107      this.completed=true;
108      this.nativeSession.discardChanges();
109    }
110
111    void dispose() throws ApplicationContextException {
112      if(!this.dispossed) {
113        this.dispossed=true;
114        ApplicationContext.this.session.remove();
115        ApplicationContext.this.delegate.terminateSession(this.nativeSession);
116      }
117    }
118
119  }
120
121  private static final Logger LOGGER=LoggerFactory.getLogger(ApplicationContext.class);
122
123  private final RuntimeDelegate delegate;
124
125  private final ThreadLocal<WriteSession> session;
126
127  private ApplicationContext() {
128    this.delegate=RuntimeDelegate.getInstance();
129    this.session=new ThreadLocal<WriteSession>();
130    LOGGER.info("Initialized Application Context");
131  }
132
133  private ApplicationContextException failure(Throwable cause, String fmt, Object... args) {
134    String message=String.format(fmt,args);
135
136    if(cause!=null) {
137      LOGGER.error(message+". Full stacktrace follows",cause);
138    } else {
139      LOGGER.error(message);
140    }
141
142    return new ApplicationContextException(message,cause);
143  }
144
145  /**
146   * Create a {@code WriteSession}. Only one write session can be active per
147   * thread. Sessions should not be shared among threads.
148   *
149   * @return the write session
150   * @throws ApplicationContextException
151   *             if no write session can be created for whichever reason,
152   *             e.g., the Application Engine is off-line or is not available.
153   */
154  public WriteSession createSession() throws ApplicationContextException {
155    if(this.session.get()!=null) {
156      throw failure(null,"Thread already has an active session");
157    }
158    try {
159      if(this.delegate.isOffline()) {
160        throw failure(null,"The Application Engine is off-line");
161      }
162      return new SafeWriteSession(this.delegate.createSession());
163    } catch (UnsupportedOperationException e) {
164      throw failure(e,"No Application Engine is available");
165    }
166  }
167
168
169  /**
170   * Dispose a {@code WriteSession}. Once the session has been disposed it
171   * will not be active (usable) any longer.
172   *
173   * @param session
174   *            the session to be disposed
175   * @throws NullPointerException
176   *             if the session is null.
177   * @throws IllegalArgumentException
178   *             if the session to be disposed was not created by the
179   *             Application Context.
180   * @throws ApplicationContextException
181   *             if the session cannot be disposed, e.g., the session is not
182   *             owned by the current thread.
183   */
184  public void disposeSession(WriteSession session) throws ApplicationContextException {
185    checkNotNull(session,"Session cannot be null");
186    checkArgument(session instanceof SafeWriteSession,"Unknown session");
187    if(this.session.get()!=session) {
188      throw failure(null,"Session '%s' is not owned by current thread",session);
189    }
190    SafeWriteSession safeWriteSession = (SafeWriteSession)session;
191    safeWriteSession.dispose();
192  }
193
194
195  /**
196   * Get the Application context.
197   *
198   * @return the application context.
199   */
200  public static ApplicationContext getInstance() {
201    return ApplicationEngineSingleton.SINGLETON;
202  }
203
204}