org.alfresco.wcm.client.impl.AbstractCachingSectionFactoryImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.alfresco.wcm.client.impl.AbstractCachingSectionFactoryImpl.java

Source

/*
 * #%L
 * Alfresco WCMQS Client API
 * %%
 * Copyright (C) 2005 - 2016 Alfresco Software Limited
 * %%
 * This file is part of the Alfresco software. 
 * If the software was purchased under a paid Alfresco license, the terms of 
 * the paid license agreement will prevail.  Otherwise, the software is 
 * provided under the following open source license terms:
 * 
 * Alfresco is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Alfresco is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 * #L%
 */
package org.alfresco.wcm.client.impl;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentSkipListMap;

import org.alfresco.wcm.client.AssetFactory;
import org.alfresco.wcm.client.CollectionFactory;
import org.alfresco.wcm.client.DictionaryService;
import org.alfresco.wcm.client.Section;
import org.alfresco.wcm.client.SectionFactory;
import org.alfresco.wcm.client.Tag;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * Factory class for creating Sections from the repository. This abstract implementation handles all the necessary caching logic.
 * Concrete implementations just need to implement the findSectionWithChildren(String) operation.
 * 
 * @author Chris Lack
 * @author Brian Remmington
 */
public abstract class AbstractCachingSectionFactoryImpl implements SectionFactory {
    protected static final String PROPERTY_TAG_SUMMARY = "cm:tagScopeSummary";

    private final static Log log = LogFactory.getLog(AbstractCachingSectionFactoryImpl.class);

    private long sectionsRefreshAfter;

    /**
     * Map of sections. If a section is present then all of its descendents will
     * be too. (Note: All sections for a website will only be present when
     * rootSectionsByWebsite has also been populated.)
     */
    private ConcurrentMap<String, Section> sectionsById = new ConcurrentSkipListMap<String, Section>();

    /** Cache of all sections under a website */
    private Map<String, SectionCache> rootSectionsByWebsite = new ConcurrentSkipListMap<String, SectionCache>();

    private ConcurrentMap<String, String> sectionsBeingLoaded = new ConcurrentSkipListMap<String, String>();

    private AssetFactory assetFactory;
    private DictionaryService dictionaryService;
    private CollectionFactory collectionFactory;

    /**
     * Set the asset factory
     * 
     * @param assetFactory
     *            asset factory
     */
    public void setAssetFactory(AssetFactory assetFactory) {
        this.assetFactory = assetFactory;
    }

    /**
     * Set the dictionary service
     * 
     * @param dictionaryService
     *            dictionary service
     */
    public void setDictionaryService(DictionaryService dictionaryService) {
        this.dictionaryService = dictionaryService;
    }

    public void setCollectionFactory(CollectionFactory collectionFactory) {
        this.collectionFactory = collectionFactory;
    }

    public AssetFactory getAssetFactory() {
        return assetFactory;
    }

    public DictionaryService getDictionaryService() {
        return dictionaryService;
    }

    public CollectionFactory getCollectionFactory() {
        return collectionFactory;
    }

    /**
     * Create list of tag details from separate lists of names and counts
     * 
     * @param tagSummary List<String>
     * @return combined list of tags
     */
    protected List<Tag> createTags(List<String> tagSummary) {
        List<Tag> tags = new ArrayList<Tag>();
        if (tagSummary != null) {
            for (String tag : tagSummary) {
                String[] nameCountPair = tag.split("=");
                if (nameCountPair.length != 2) {
                    continue;
                }
                try {
                    tags.add(new TagImpl(nameCountPair[0], Integer.parseInt(nameCountPair[1])));
                } catch (Exception ex) {
                    log.warn("Ignoring invalid tag summary data: " + tag);
                }
            }
        }
        return tags;
    }

    /**
     * @see org.alfresco.wcm.client.SectionFactory#getSection(String)
     */
    @Override
    public Section getSection(String id) {
        // Try cache
        Section section = sectionsById.get(id);

        // Not in cache so fetch
        if (section == null) {
            Map<String, Section> loadedSections = findSectionWithChildren(id);
            if (loadedSections != null) {
                sectionsById.putAll(loadedSections);
                section = sectionsById.get(id);
            }
        }
        return section;
    }

    /**
     * @see org.alfresco.wcm.client.SectionFactory#getSectionFromPathSegments(String,
     *      String[])
     */
    @Override
    public Section getSectionFromPathSegments(String rootSectionId, String[] pathSegments) {
        refreshCacheIfRequired(rootSectionId);

        SectionCache cache = rootSectionsByWebsite.get(rootSectionId);
        Section currentSection = cache.rootSection;

        for (String segment : pathSegments) {
            if (segment.length() > 0)
                currentSection = currentSection.getSection(segment);
            if (currentSection == null)
                return null;
        }
        return currentSection;
    }

    /**
     * Refreshes the section cache if empty or expired.
     * 
     * @param rootSectionId
     *            the id of the parent web root
     */
    private void refreshCacheIfRequired(String rootSectionId) {
        SectionCache cache = rootSectionsByWebsite.get(rootSectionId);
        while (cache == null || cache.isExpired()) {
            String existingSectionToken = sectionsBeingLoaded.putIfAbsent(rootSectionId, rootSectionId);
            if (existingSectionToken == null) {
                try {
                    //It looks like we have to load this section tree, 
                    //but before we do, let's just check that another thread hasn't got in between us checking the cache
                    //and checking whether it's already being loaded...
                    cache = rootSectionsByWebsite.get(rootSectionId);
                    if (cache != null && !cache.isExpired()) {
                        //This section is now in the cache, so we don't need to do anything 
                        return;
                    }
                    if (log.isDebugEnabled()) {
                        log.debug(Thread.currentThread().getName() + " started refreshing tree cache for section "
                                + rootSectionId);
                    }

                    //This section isn't currently being loaded. Load it.
                    Map<String, Section> sections = findSectionWithChildren(rootSectionId);
                    Section rootSection = sections.get(rootSectionId);
                    SectionCache cachedRootSection = new SectionCache(rootSection);
                    rootSectionsByWebsite.put(rootSectionId, cachedRootSection);
                    sectionsById.putAll(sections);
                    if (log.isDebugEnabled()) {
                        log.debug(Thread.currentThread().getName() + " finished refreshing tree cache for section "
                                + rootSectionId);
                    }
                    return;
                } finally {
                    //There may be other threads waiting for us to finish loading this section tree.
                    //Let them know that we've finished
                    synchronized (rootSectionId) {
                        sectionsBeingLoaded.remove(rootSectionId);
                        rootSectionId.notifyAll();
                    }
                }
            } else {
                //This section is currently being loaded
                if (cache != null) {
                    //We are currently refreshing the cache, but the requested section does already 
                    //appear in the cache. Therefore we'll let the caller simply use the currently-cached 
                    //copy
                    return;
                } else {
                    //This section isn't currently cached, but is already being loaded
                    //Wait for it to be loaded...
                    synchronized (existingSectionToken) {
                        if (sectionsBeingLoaded.containsKey(existingSectionToken)) {
                            try {
                                if (log.isDebugEnabled()) {
                                    log.debug(Thread.currentThread().getName()
                                            + " started waiting for section tree to be loaded " + rootSectionId);
                                }
                                existingSectionToken.wait();
                            } catch (InterruptedException e) {
                            }
                            if (log.isDebugEnabled()) {
                                log.debug(Thread.currentThread().getName()
                                        + " finished waiting for section tree to be loaded " + rootSectionId);
                            }
                        }
                    }
                }
            }
            cache = rootSectionsByWebsite.get(rootSectionId);
        }
    }

    /**
     * Fetch a section and its children.
     * 
     * @param topSectionId
     *            the section id to start from
     * @return the section object with its children populated.
     */
    protected abstract Map<String, Section> findSectionWithChildren(String topSectionId);

    public void setSectionsRefreshAfter(int seconds) {
        this.sectionsRefreshAfter = seconds * 1000;
    }

    /**
     * Section with parent id. Used until the section is parented.
     */
    protected class SectionDetails {
        SectionImpl section;
        String objectTypeId;
        String parentId;
    }

    /**
     * A root section and the time the data was cached.
     */
    private class SectionCache {
        Section rootSection;
        long sectionsRefeshedAt;

        SectionCache(Section root) {
            this.rootSection = root;
            this.sectionsRefeshedAt = System.currentTimeMillis();
        }

        /**
         * Indicates whether the sections cache has expired or not
         * 
         * @return boolean true if expired, false otherwise
         */
        boolean isExpired() {
            long now = System.currentTimeMillis();
            long difference = now - sectionsRefeshedAt;
            return difference > sectionsRefreshAfter;
        }
    }

}