package org.eclipse.jst.jsf.facelet.core.internal.registry; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentLinkedQueue; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.MultiStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.ILock; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jst.jsf.common.internal.managedobject.IManagedObject; import org.eclipse.jst.jsf.common.internal.managedobject.ObjectManager.ManagedObjectException; import org.eclipse.jst.jsf.common.internal.policy.IdentifierOrderedIteratorPolicy; import org.eclipse.jst.jsf.common.runtime.internal.view.model.common.Namespace; import org.eclipse.jst.jsf.core.internal.JSFCorePlugin; import org.eclipse.jst.jsf.core.internal.JSFCoreTraceOptions; import org.eclipse.jst.jsf.designtime.internal.view.model.AbstractTagRegistry; import org.eclipse.jst.jsf.designtime.internal.view.model.jsp.CompositeTagResolvingStrategy; import org.eclipse.jst.jsf.facelet.core.internal.FaceletCorePlugin; import org.eclipse.jst.jsf.facelet.core.internal.FaceletCoreTraceOptions; import org.eclipse.jst.jsf.facelet.core.internal.cm.FaceletDocumentFactory; import org.eclipse.jst.jsf.facelet.core.internal.registry.IFaceletTagResolvingStrategy.TLDWrapper; import org.eclipse.jst.jsf.facelet.core.internal.registry.taglib.FaceletTagIndex; import org.eclipse.jst.jsf.facelet.core.internal.registry.taglib.IFaceletTagRecord; import org.eclipse.jst.jsf.facelet.core.internal.registry.taglib.IProjectTaglibDescriptor; import org.eclipse.jst.jsf.facelet.core.internal.registry.taglib.Listener; import org.eclipse.jst.jsf.facelet.core.internal.tagmodel.FaceletNamespace; /** * Registry of all facelet tag registries: at most one per project. * */ public final class FaceletTagRegistry extends AbstractTagRegistry implements IManagedObject { // INSTANCE private final ConcurrentLinkedQueue<LibraryOperation> _changeOperations = new ConcurrentLinkedQueue<LibraryOperation>(); private final IProject _project; private final Map<String, FaceletNamespace> _nsResolved; private final Set<FaceletNamespace> _unResolved; private final CompositeTagResolvingStrategy<TLDWrapper> _resolver; private final FaceletDocumentFactory _factory; private final LibraryOperationFactory _operationFactory = new LibraryOperationFactory( this); private final ILock _lock = Job.getJobManager().newLock(); private volatile boolean _isInitialized; private ChangeJob _changeJob; private MyTaglibListener _listener; FaceletTagRegistry(final IProject project) { _project = project; _nsResolved = new HashMap<String, FaceletNamespace>(); _unResolved = new HashSet<FaceletNamespace>(); final List<String> ids = new ArrayList<String>(); //Commenting out this strategy because of current circular dependency with facelet md locating. See FaceletNamespaceMetaDataLocator. // ids.add(FaceletMetaResolvingStrategy.ID); ids.add(FaceletTagResolvingStrategy.ID); final IdentifierOrderedIteratorPolicy<String> policy = new IdentifierOrderedIteratorPolicy<String>( ids); // exclude things that are not explicitly listed in the policy. That // way preference-based disablement will cause those strategies to // be excluded. policy.setExcludeNonExplicitValues(true); _resolver = new CompositeTagResolvingStrategy<TLDWrapper>(policy); _factory = new FaceletDocumentFactory(project); // add the strategies _resolver.addStrategy(new FaceletTagResolvingStrategy(_project, _factory)); //Commenting out this strategy because of current circular dependency with facelet md locating. See FaceletNamespaceMetaDataLocator. // _resolver.addStrategy(new FaceletMetaResolvingStrategy(_project, _factory)); // _resolver.addStrategy(new DefaultJSPTagResolver(_project)); // makes sure that a tag element will always be created for any // given tag definition even if other methods fail // _resolver.addStrategy(new UnresolvedJSPTagResolvingStrategy()); _changeJob = new ChangeJob(project.getName()); } /** * @return a copy of all tag libs, both with namespaces resolved and without * Changing the returned may has no effect on the registry, however * the containned objects are not copies. */ @Override public Collection<FaceletNamespace> getAllTagLibraries() { boolean setEndRule = false; try { final Set<FaceletNamespace> allTagLibraries = new HashSet<FaceletNamespace>(); if (!_isInitialized) { // preemptive project rule setting here ensures consistent lock ordering // and gives the opportunity for the other thread having the project lock // to finish before we enter synchronization block created with reentrant // lock below // NOTE: it is essential to have _lock.acquire() after project rule start // NOTE: if current thread already has any rule, do not start project rule if(Job.getJobManager().currentRule() == null){ Job.getJobManager().beginRule(_project, null); setEndRule = true; } _lock.acquire(); // double check after sync block if no one else entered "if(!_isInitialized)" if(!_isInitialized){ try { initialize(false); _isInitialized = true; } catch (final JavaModelException e) { FaceletCorePlugin.log("Problem during initialization", e); //$NON-NLS-1$ } catch (final CoreException e) { FaceletCorePlugin.log("Problem during initialization", e); //$NON-NLS-1$ } } }else{ _lock.acquire(); } allTagLibraries.addAll(_nsResolved.values()); allTagLibraries.addAll(_unResolved); return allTagLibraries; } finally { _lock.release(); if (setEndRule){ Job.getJobManager().endRule(_project); } } } private void initialize(boolean fireEvent) throws JavaModelException, CoreException { if (!_project.exists() || !_project.hasNature(JavaCore.NATURE_ID)) { throw new CoreException(new Status(IStatus.ERROR, FaceletCorePlugin.PLUGIN_ID, "Project either does not exists or is not a java project: " //$NON-NLS-1$ + _project)); } final FaceletTagIndex index = FaceletTagIndex.getInstance(_project.getWorkspace()); IProjectTaglibDescriptor tagDesc; try { tagDesc = index.getInstance(_project); } catch (ManagedObjectException e) { throw new CoreException( new Status( IStatus.ERROR, FaceletCorePlugin.PLUGIN_ID, "Error instantiating facelet tag index for project: " + _project.getName(), e)); //$NON-NLS-1$ } if (tagDesc != null) { for (final IFaceletTagRecord taglib : tagDesc.getTagLibraries()) { if (taglib.getURI() != null) initialize(taglib, fireEvent); } _listener = new MyTaglibListener(); tagDesc.addListener(_listener); } } FaceletNamespace initialize(final IFaceletTagRecord tagRecord, final boolean fireEvent) { if (JSFCoreTraceOptions.TRACE_JSPTAGREGISTRY_CHANGES) { FaceletCoreTraceOptions .log("TLDTagRegistry.initialize_TagRecord: Initializing new tld record: " + tagRecord.toString()); //$NON-NLS-1$ } final FaceletNamespace ns = new FaceletNamespace(tagRecord, _resolver); _nsResolved.put(tagRecord.getURI(), ns); if (fireEvent) { fireEvent(new TagRegistryChangeEvent(this, TagRegistryChangeEvent.EventType.ADDED_NAMESPACE, Collections.singletonList(ns))); } return ns; } void remove(final IFaceletTagRecord tagRecord) { final FaceletNamespace ns = _nsResolved.remove(tagRecord.getURI()); if (ns != null) { fireEvent(new TagRegistryChangeEvent(this, TagRegistryChangeEvent.EventType.REMOVED_NAMESPACE, Collections.singletonList(ns))); } } @Override public Namespace getTagLibrary(final String uri) { // TODO: getAllTagLibraries(); return _nsResolved.get(uri); } @Override protected Job getRefreshJob(final boolean flushCaches) { return new Job("Refreshing Facelet tag registry for " + _project.getName()) //$NON-NLS-1$ { @Override protected IStatus run(final IProgressMonitor monitor) { // if (FaceletCoreTraceOptions.TRACE_JSPTAGREGISTRY) // { // JSFCoreTraceOptions.log("FaceletTagRegistry.refresh: start"); //$NON-NLS-1$ // } boolean setEndRule = false; try { if(Job.getJobManager().currentRule() == null){ Job.getJobManager().beginRule(_project, null); setEndRule = true; } _lock.acquire(); if (JSFCoreTraceOptions.TRACE_JSPTAGREGISTRY) { JSFCoreTraceOptions .log("FaceletTagRegistry.refresh: start"); //$NON-NLS-1$ } final List<Namespace> namespaces = new ArrayList( _nsResolved.values()); if (flushCaches) { FaceletTagIndex.getInstance(_project.getWorkspace()).flush(_project); } // if we aren't flushing caches, then check point the // current namespace data, so it isn't lost when we clear // THE NAMESPACES else { checkpoint(); } _nsResolved.clear(); fireEvent(new TagRegistryChangeEvent(FaceletTagRegistry.this, TagRegistryChangeEvent.EventType.REMOVED_NAMESPACE, namespaces)); try { initialize(true); } catch (JavaModelException e) { return new Status(IStatus.ERROR, FaceletCorePlugin.PLUGIN_ID, "Problem refreshing registry", e); //$NON-NLS-1$ } catch (CoreException e) { return new Status(IStatus.ERROR, FaceletCorePlugin.PLUGIN_ID, "Problem refreshing registry", e); //$NON-NLS-1$ } // if (JSFCoreTraceOptions.TRACE_JSPTAGREGISTRY) // { // JSFCoreTraceOptions // .log("TLDTagRegistry.refresh: finished"); // } return Status.OK_STATUS; } finally { _lock.release(); if (setEndRule){ Job.getJobManager().endRule(_project); } } } }; } private class MyTaglibListener extends Listener { @Override public void changed(TaglibChangedEvent event) { switch (event.getChangeType()) { case ADDED: addLibraryOperation(_operationFactory .createAddOperation(event.getNewValue())); break; case CHANGED: addLibraryOperation(_operationFactory .createChangeOperation(event.getNewValue())); break; case REMOVED: addLibraryOperation(_operationFactory .createRemoveOperation(event.getOldValue())); break; } } } private void addLibraryOperation(final LibraryOperation operation) { _changeOperations.add(operation); _changeJob.schedule(); } private class ChangeJob extends Job { private int _rescheduleTime = -1; public ChangeJob(final String projectName) { super("Update job for project " + projectName); //$NON-NLS-1$ // preemptive project rule setting here ensures consistent lock ordering // and gives the opportunity for the other thread having the project lock // to finish before we enter synchronization block created with reentrant // lock below // NOTE: it is essential to have _lock.acquire() after project rule start setRule(_project); } @Override protected IStatus run(final IProgressMonitor monitor) { try { _lock.acquire(); _rescheduleTime = -1; LibraryOperation operation = null; final MultiStatus multiStatus = new MultiStatus( JSFCorePlugin.PLUGIN_ID, 0, "Result of change job", //$NON-NLS-1$ new Throwable()); while ((operation = _changeOperations.poll()) != null) { _rescheduleTime = 10000; // ms operation.run(); multiStatus.add(operation.getResult()); } if (_rescheduleTime >= 0 && !monitor.isCanceled()) { // if any operations were found on this run, reschedule // to run again in 10seconds based on the assumption that // events may be coming in bursts schedule(_rescheduleTime); } return multiStatus; } finally { _lock.release(); } } } @Override protected void doDispose() { if (_listener != null) { FaceletTagIndex index = FaceletTagIndex.getInstance(_project.getWorkspace()); try { IProjectTaglibDescriptor instance = index.getInstance(_project); instance.removeListener(_listener); } catch (ManagedObjectException e) { FaceletCorePlugin .log( "Disposing facelet tag registry for project: " + _project.getName(), e); //$NON-NLS-1$ } _nsResolved.clear(); } } @Override protected void cleanupPersistentState() { // TODO ?? } public void checkpoint() { // TODO ?? } }