001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.cocoon.jcr;
018:
019: import org.apache.avalon.framework.activity.Disposable;
020: import org.apache.avalon.framework.component.Component;
021: import org.apache.avalon.framework.configuration.Configurable;
022: import org.apache.avalon.framework.configuration.Configuration;
023: import org.apache.avalon.framework.configuration.ConfigurationException;
024: import org.apache.avalon.framework.context.Context;
025: import org.apache.avalon.framework.context.ContextException;
026: import org.apache.avalon.framework.context.Contextualizable;
027: import org.apache.avalon.framework.logger.AbstractLogEnabled;
028: import org.apache.avalon.framework.service.ServiceException;
029: import org.apache.avalon.framework.service.ServiceManager;
030: import org.apache.avalon.framework.service.Serviceable;
031: import org.apache.avalon.framework.thread.ThreadSafe;
032:
033: import org.apache.cocoon.components.ContextHelper;
034: import org.apache.cocoon.components.treeprocessor.variables.VariableResolver;
035: import org.apache.cocoon.components.treeprocessor.variables.VariableResolverFactory;
036: import org.apache.cocoon.environment.ObjectModelHelper;
037: import org.apache.cocoon.environment.Request;
038: import org.apache.cocoon.sitemap.PatternException;
039:
040: import org.apache.excalibur.source.SourceResolver;
041: import org.apache.excalibur.source.impl.FileSource;
042:
043: import javax.jcr.Credentials;
044: import javax.jcr.LoginException;
045: import javax.jcr.NoSuchWorkspaceException;
046: import javax.jcr.Repository;
047: import javax.jcr.RepositoryException;
048: import javax.jcr.Session;
049: import javax.jcr.SimpleCredentials;
050: import java.util.Map;
051:
052: /**
053: * Base class for JCR (aka <a
054: * href="http://www.jcp.org/en/jsr/detail?id=170">JSR-170</a>) repository as
055: * a Cocoon component. The main purpose of this class is to allow repository
056: * credentials to be specified in the component's configuration, so that the
057: * application code just has to call <code>repository.login()</code>.
058: *
059: * <p>
060: * There is no Cocoon-specific role for this component: "<code>javax.jcr.Repository</code>"
061: * should be used.
062: *
063: * <p>
064: * The configuration of this class, inherited by its subclasses, is as follows:
065: *
066: * <pre>
067: * <jcr-repository>
068: * <jaas src="context://samples/jaas.config"/>
069: * <credentials login="<i>expression</i>" password="<i>expression</i>"/>
070: * ... other specific configuration...
071: * </jcr-repository>
072: * </pre>
073: *
074: * Login and password can be specified using the sitemap expression language,
075: * thus allowing the use of input modules to compute their values, e.g.
076: * <code>password="{session-attr:jcr-password}"</code>.
077: *
078: * <p>
079: * <code><credentials></code> is optional. If not specified, the
080: * application must explicitely supply credentials when calling
081: * <code>Repository.login()</code>.
082: *
083: * @version $Id: AbstractRepository.java 449153 2006-09-23 04:27:50Z crossley $
084: */
085: public abstract class AbstractRepository extends AbstractLogEnabled
086: implements Repository, ThreadSafe, Contextualizable,
087: Serviceable, Configurable, Disposable, Component {
088:
089: /**
090: * Role which shall be used for JCR repository implementations.
091: */
092: public static final String ROLE = "javax.jcr.Repository";
093:
094: /**
095: * The request attribute in which the JCR session is stored
096: */
097: public static final String JCR_SESSION_REQUEST_ATTRIBUTE = "jcr-session";
098:
099: protected ServiceManager manager;
100:
101: protected Context context;
102:
103: protected Repository delegate;
104:
105: // Defined by the portal block :-(
106: // protected VariableResolverFactory variableFactory;
107:
108: protected VariableResolver loginResolver;
109:
110: protected VariableResolver passwordResolver;
111:
112: // =============================================================================================
113: // Avalon lifecycle
114: // =============================================================================================
115:
116: public void contextualize(Context context) throws ContextException {
117: this .context = context;
118: }
119:
120: public void service(ServiceManager manager) throws ServiceException {
121: this .manager = manager;
122: }
123:
124: public void configure(Configuration config)
125: throws ConfigurationException {
126: // FIXME FIXME FIXME Hack setting system jaas property
127: Configuration jaas = config.getChild("jaas", false);
128: if (jaas != null) {
129: String jaasURI = jaas.getAttribute("src");
130: SourceResolver resolver = null;
131: FileSource jaasSrc = null;
132: try {
133: resolver = (SourceResolver) this .manager
134: .lookup(SourceResolver.ROLE);
135: jaasSrc = (FileSource) resolver.resolveURI(jaasURI);
136: if (System
137: .getProperty("java.security.auth.login.config") == null) {
138: System.setProperty(
139: "java.security.auth.login.config", jaasSrc
140: .getFile().getAbsolutePath());
141: } else {
142: // WARNING: java.security.auth.login.config has already been set
143: }
144: } catch (Exception e) {
145: throw new ConfigurationException(
146: "Cannot resolve jaas URI: " + jaasURI + " at "
147: + config.getLocation());
148: } finally {
149: if (jaasSrc != null) {
150: resolver.release(jaasSrc);
151: }
152: this .manager.release(resolver);
153: }
154: }
155:
156: Configuration credentials = config.getChild("credentials",
157: false);
158: if (credentials != null) {
159: String login = credentials.getAttribute("login");
160: String password = credentials.getAttribute("password");
161:
162: try {
163: this .loginResolver = VariableResolverFactory
164: .getResolver(login, this .manager);
165: } catch (PatternException e) {
166: throw new ConfigurationException(
167: "Invalid expression for 'login' at "
168: + credentials.getLocation(), e);
169: }
170: try {
171: this .passwordResolver = VariableResolverFactory
172: .getResolver(password, this .manager);
173: } catch (PatternException e) {
174: if (this .loginResolver instanceof Disposable) {
175: ((Disposable) this .loginResolver).dispose();
176: }
177: this .loginResolver = null;
178: throw new ConfigurationException(
179: "Invalid expression for 'password' at "
180: + credentials.getLocation(), e);
181: }
182: }
183: }
184:
185: public void dispose() {
186: this .context = null;
187: this .delegate = null;
188:
189: if (this .loginResolver instanceof Disposable) {
190: ((Disposable) this .loginResolver).dispose();
191: }
192: this .loginResolver = null;
193: if (this .passwordResolver instanceof Disposable) {
194: ((Disposable) this .passwordResolver).dispose();
195: }
196: this .passwordResolver = null;
197: this .manager = null;
198: }
199:
200: // =============================================================================================
201: // Repository interface
202: // =============================================================================================
203:
204: public String getDescriptor(String key) {
205: return delegate.getDescriptor(key);
206: }
207:
208: public String[] getDescriptorKeys() {
209: return delegate.getDescriptorKeys();
210: }
211:
212: public Session login() throws LoginException,
213: NoSuchWorkspaceException, RepositoryException {
214: Session session = getCachedSession(null);
215:
216: if (session == null) {
217: Credentials creds = getCredentials();
218: session = creds == null ? delegate.login() : delegate
219: .login(creds);
220: cacheSession(session, null);
221: }
222:
223: return session;
224: }
225:
226: public Session login(Credentials creds) throws LoginException,
227: NoSuchWorkspaceException, RepositoryException {
228: Session session = getCachedSession(null);
229:
230: if (session == null) {
231: session = delegate.login(creds);
232: cacheSession(session, null);
233: }
234:
235: return session;
236: }
237:
238: public Session login(Credentials creds, String workspace)
239: throws LoginException, NoSuchWorkspaceException,
240: RepositoryException {
241: Session session = getCachedSession(workspace);
242:
243: if (session == null) {
244: session = delegate.login(creds, workspace);
245: cacheSession(session, workspace);
246: }
247:
248: return session;
249: }
250:
251: public Session login(String workspace) throws LoginException,
252: NoSuchWorkspaceException, RepositoryException {
253: Session session = getCachedSession(workspace);
254:
255: if (session == null) {
256: Credentials creds = getCredentials();
257: session = creds == null ? delegate.login(workspace)
258: : delegate.login(creds, workspace);
259: cacheSession(session, workspace);
260: }
261:
262: return session;
263: }
264:
265: // TODO: When logout should be called?
266:
267: // =============================================================================================
268: // Implementation methods
269: // =============================================================================================
270:
271: private Session getCachedSession(String workspace) {
272: Map objectModel;
273: try {
274: objectModel = ContextHelper.getObjectModel(context);
275: } catch (Exception e) {
276: // We don't have an object model (happens e.g. at init time or in a cron job)
277: return null;
278: }
279:
280: String attributeName = workspace == null ? JCR_SESSION_REQUEST_ATTRIBUTE
281: : JCR_SESSION_REQUEST_ATTRIBUTE + "/" + workspace;
282:
283: Request request = ObjectModelHelper.getRequest(objectModel);
284: //FIXME: request is null when running in a testcase
285: if (request == null)
286: return null;
287: Session session = (Session) request.getAttribute(attributeName);
288:
289: return (session != null && session.isLive()) ? session : null;
290: }
291:
292: private void cacheSession(Session session, String workspace) {
293: Map objectModel;
294: try {
295: objectModel = ContextHelper.getObjectModel(context);
296: } catch (Exception e) {
297: // We don't have an object model (happens e.g. at init time or in a cron job)
298: return;
299: }
300:
301: String attributeName = workspace == null ? JCR_SESSION_REQUEST_ATTRIBUTE
302: : JCR_SESSION_REQUEST_ATTRIBUTE + "/" + workspace;
303:
304: Request request = ObjectModelHelper.getRequest(objectModel);
305: //FIXME: request is null when running in a testcase
306: if (request == null)
307: return;
308: request.setAttribute(attributeName, session);
309: }
310:
311: private Credentials getCredentials() throws LoginException {
312: if (this .loginResolver != null) {
313:
314: Map objectModel;
315: try {
316: objectModel = ContextHelper.getObjectModel(context);
317: } catch (Exception e) {
318: // We don't have an object model (happens e.g. at init time or in a cron job)
319: throw new LoginException(
320: "No objectModel to evaluate credentials", e);
321: }
322:
323: try {
324: String login = this .loginResolver.resolve(objectModel);
325: String password = this .loginResolver
326: .resolve(objectModel);
327: return new SimpleCredentials(login, password
328: .toCharArray());
329: } catch (PatternException e) {
330: throw new LoginException(
331: "Failed to evaluate credentials", e);
332: }
333: } else {
334: return null;
335: }
336: }
337: }
|