001/*
002 *  Copyright 2010 Anyware Services
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
017package org.ametys.plugins.repository.provider;
018
019import java.io.File;
020import java.util.HashMap;
021import java.util.Map;
022
023import javax.jcr.LoginException;
024import javax.jcr.NoSuchWorkspaceException;
025import javax.jcr.RepositoryException;
026import javax.jcr.Session;
027import javax.jcr.SimpleCredentials;
028
029import org.apache.avalon.framework.CascadingRuntimeException;
030import org.apache.avalon.framework.activity.Disposable;
031import org.apache.avalon.framework.activity.Initializable;
032import org.apache.avalon.framework.component.Component;
033import org.apache.avalon.framework.context.ContextException;
034import org.apache.avalon.framework.context.Contextualizable;
035import org.apache.avalon.framework.service.ServiceException;
036import org.apache.avalon.framework.service.ServiceManager;
037import org.apache.cocoon.Constants;
038import org.apache.cocoon.components.ContextHelper;
039import org.apache.cocoon.environment.Context;
040import org.apache.cocoon.environment.Request;
041import org.apache.jackrabbit.core.cache.CacheManager;
042import org.apache.jackrabbit.core.config.ConfigurationException;
043import org.apache.jackrabbit.core.config.RepositoryConfig;
044
045import org.ametys.core.datasource.ConnectionHelper;
046import org.ametys.plugins.repository.RepositoryConstants;
047import org.ametys.runtime.config.Config;
048import org.ametys.runtime.util.AmetysHomeHelper;
049
050/**
051 * JackrabbitRepository is a JCR repository component based on Jackrabbit
052 */
053public class JackrabbitRepository extends AbstractRepository implements LogoutManager, Initializable, Contextualizable, Disposable, Component
054{
055    private org.apache.avalon.framework.context.Context _avalonContext;
056    private Context _context;
057
058    @Override
059    public void contextualize(org.apache.avalon.framework.context.Context context) throws ContextException
060    {
061        _avalonContext = context;
062        _context = (Context) context.get(Constants.CONTEXT_ENVIRONMENT_CONTEXT);
063    }
064    
065    @Override
066    public void service(ServiceManager smanager) throws ServiceException
067    {
068        super.service(smanager);
069        // Make sure the ConnectionHelper is initialized first, so the SQLDataSourceManager can be used in AmetysPersistenceManager
070        smanager.lookup(ConnectionHelper.ROLE);
071    }
072    
073    
074    public void initialize() throws Exception
075    {
076        RepositoryConfig repositoryConfig = createRepositoryConfig();
077        AmetysRepository repo = new AmetysRepository(repositoryConfig);
078        
079        _delegate = repo;
080        ((AmetysRepository) _delegate).setLogoutManager(this);
081        
082        Long cacheSize = Config.getInstance().getValueAsLong("org.ametys.plugins.repository.cache");  
083        if (cacheSize == null || cacheSize <= 0)
084        {
085            cacheSize = 16777216L; // default value;
086        }
087        
088        CacheManager cacheManager = repo.getCacheManager();
089        cacheManager.setMaxMemory(cacheSize);
090        cacheManager.setMaxMemoryPerCache(cacheSize / 4);
091        cacheManager.setMinMemoryPerCache(cacheSize / 128);
092        
093        // Register the delegate repository in the context for the repository workspace.
094        _context.setAttribute(CONTEXT_REPOSITORY_KEY, _delegate);
095        _context.setAttribute(CONTEXT_CREDENTIALS_KEY, new SimpleCredentials("ametys", new char[]{}));
096        _context.setAttribute(CONTEXT_IS_JNDI_KEY, false);
097        
098        if (getLogger().isDebugEnabled())
099        {
100            getLogger().debug("JCR Repository running");
101        }
102    }
103
104    public void dispose()
105    {
106        AmetysRepository repo = (AmetysRepository) _delegate;
107        repo.setLogoutManager(null);
108        repo.shutdown();
109    }    
110    
111    /**
112     * Create the repository configuration from the configuration file
113     * @return The repository configuration
114     * @throws ConfigurationException if an error occurred
115     */
116    RepositoryConfig createRepositoryConfig() throws ConfigurationException
117    {
118        String config = _context.getRealPath("/WEB-INF/param/repository.xml");
119        
120        File homeFile = new File(AmetysHomeHelper.getAmetysHomeData(), "repository");
121        
122        if (getLogger().isDebugEnabled())
123        {
124            getLogger().debug("Creating JCR Repository config at: " + homeFile.getAbsolutePath());
125        }
126        
127        return RepositoryConfig.create(config, homeFile.getAbsolutePath());
128    }
129
130    int getSessionCount()
131    {
132        return ((AmetysRepository) _delegate).getSessionCount();
133    }
134    
135    public Session login(String workspace) throws LoginException, NoSuchWorkspaceException, RepositoryException
136    {
137        try
138        {
139            if (getLogger().isDebugEnabled())
140            {
141                getLogger().debug("Getting JCR Session");
142            }
143            
144            try
145            {
146                Request request = ContextHelper.getRequest(_avalonContext);
147                
148                @SuppressWarnings("unchecked")
149                Map<String, Session> sessions = (Map<String, Session>) request.getAttribute(RepositoryConstants.JCR_SESSION_REQUEST_ATTRIBUTE);
150                
151                if (sessions == null)
152                {
153                    sessions = new HashMap<>();
154                    request.setAttribute(RepositoryConstants.JCR_SESSION_REQUEST_ATTRIBUTE, sessions);
155                }
156                
157                Session session = sessions.get(workspace);
158                
159                if (session == null || !session.isLive())
160                {
161                    session = _delegate.login(new SimpleCredentials("ametys", new char[]{}), workspace);
162                    sessions.put(workspace, session);
163                }
164                
165                return session;
166            }
167            catch (CascadingRuntimeException e)
168            {
169                if (e.getCause() instanceof ContextException)
170                {
171                    // Unable to get request. Must be in another thread or at init time.
172                    Session session = _delegate.login(new SimpleCredentials("ametys", new char[]{}), workspace);
173                    return session;
174                }
175                else
176                {
177                    throw e;
178                }
179            }
180        }
181        catch (RepositoryException e)
182        {
183            throw e;
184        }
185        catch (Exception e)
186        {
187            throw new RuntimeException("Unable to get Session", e);
188        }
189    }
190    
191    public void logout(Session session)
192    {
193        if (!(session instanceof AmetysSession))
194        {
195            throw new IllegalArgumentException("JCR Session should be an instance of AmetysSession");
196        }
197        
198        AmetysSession ametysSession = (AmetysSession) session;
199        
200        if (getLogger().isDebugEnabled())
201        {
202            getLogger().debug("Logging out AmetysSession");
203        }
204        
205        try
206        {
207            // the following statement will fail if we are not processing a request.
208            ContextHelper.getRequest(_avalonContext);
209            
210            // does nothing as the session will be actually logged out at the end of the request.
211            if (getLogger().isDebugEnabled())
212            {
213                getLogger().debug("AmetysSession logout delayed until the end of the HTTP request.");
214            }
215            
216        }
217        catch (Exception e)
218        {
219            // unable to get request. Must be in another thread or at init time.
220            if (getLogger().isDebugEnabled())
221            {
222                getLogger().debug("Not in a request. AmetysSession will be actually logged out.");
223            }
224            
225            ametysSession.forceLogout();
226        }
227    }
228}