/* * Carrot2 project. * * Copyright (C) 2002-2016, Dawid Weiss, Stanisław Osiński. * All rights reserved. * * Refer to the full license file "carrot2.LICENSE" * in the root folder of the repository checkout or at: * http://www.carrot2.org/carrot2.LICENSE */ package org.carrot2.core; import java.io.IOException; import java.io.InputStream; import java.util.Map; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.builder.ToStringBuilder; import org.apache.commons.lang.builder.ToStringStyle; import org.carrot2.core.attribute.Init; import org.carrot2.util.CloseableUtils; import org.carrot2.util.ReflectionUtils; import org.carrot2.util.attribute.AttributeBinder; import org.carrot2.util.attribute.AttributeValueSet; import org.carrot2.util.attribute.AttributeValueSets; import org.carrot2.util.attribute.Bindable; import org.carrot2.util.attribute.BindableDescriptor; import org.carrot2.util.attribute.BindableDescriptorBuilder; import org.carrot2.util.attribute.Input; import org.carrot2.util.attribute.Output; import org.carrot2.util.attribute.Required; import org.carrot2.util.resource.IResource; import org.carrot2.util.resource.ResourceLookup; import org.carrot2.util.simplexml.PersisterHelpers; import org.simpleframework.xml.Attribute; import org.simpleframework.xml.Element; import org.simpleframework.xml.core.Commit; import org.carrot2.shaded.guava.common.base.Function; import org.carrot2.shaded.guava.common.collect.Maps; /** * Descriptor of a {@link IProcessingComponent} being part of a * {@link ProcessingComponentSuite}. */ public class ProcessingComponentDescriptor { @Attribute(name = "component-class") private String componentClassName; /** Cached component class instantiated from {@link #componentClassName}. */ private Class<? extends IProcessingComponent> componentClass; /** If not <code>null</code>, component initialization ended with an exception. */ private Throwable initializationException; @Attribute private String id; @Element private String label; @Element(required = false) private String mnemonic; @Element private String title; @Element(required = false, name = "icon-path") private String iconPath; @Element(required = false) private String description; private AttributeValueSets attributeSets; @Attribute(name = "attribute-sets-resource", required = false) private String attributeSetsResource; @Attribute(name = "attribute-set-id", required = false) private String attributeSetId; /** * Cached bindable descriptor for this component. */ private BindableDescriptor bindableDescriptor; ProcessingComponentDescriptor() { } public ProcessingComponentConfiguration getComponentConfiguration() { return new ProcessingComponentConfiguration(getComponentClass(), id, getAttributes()); } private Map<String, Object> getAttributes() { Map<String, Object> result = AttributeValueSet .getAttributeValues(getAttributeSets().getAttributeValueSet(attributeSetId, true)); if (result == null) { result = Maps.newHashMap(); } return result; } /** * @return Returns the {@link Class} object for this component. * @throws RuntimeException if the class cannot be defined for some reason (class * loader issues). */ @SuppressWarnings("unchecked") public synchronized Class<? extends IProcessingComponent> getComponentClass() { if (this.componentClass == null) { try { this.componentClass = (Class<? extends IProcessingComponent>) ReflectionUtils .classForName(componentClassName); } catch (Exception e) { throw new RuntimeException("Component class cannot be acquired: " + componentClassName, e); } } return this.componentClass; } public String getId() { return id; } public String getLabel() { return label; } public String getMnemonic() { return mnemonic; } public String getTitle() { return title; } /** * @return Returns (optional) path to the icon of this component. The interpretation * of this path is up to the application (icon resources may be placed in * various places). */ public String getIconPath() { return iconPath; } public String getDescription() { return description; } /** * @return Return the name of a resource from which {@link #getAttributeSets()} were * read or <code>null</code> if there was no such resource. */ public String getAttributeSetsResource() { return attributeSetsResource; } public AttributeValueSets getAttributeSets() { return attributeSets; } public String getAttributeSetId() { return attributeSetId; } /** * Creates a new initialized instance of the processing component corresponding to * this descriptor. The instance will be initialized with the {@link Init} attributes * from this descriptor's default attribute set. Checking whether all {@link Required} * attribute have been provided will not be made, which, when attributes of * {@link Bindable} are <code>null</code>, may cause {@link #getBindableDescriptor()} * to return incomplete descriptor. * <p> * The instance may or may not be usable for processing because the * {@link IControllerContext} on which it is initialized is disposed before the value * is returned. * </p> */ private IProcessingComponent newInitializedInstance() throws InstantiationException, IllegalAccessException { final IProcessingComponent instance = getComponentClass().newInstance(); final Map<String, Object> initAttributes = Maps.newHashMap(); final AttributeValueSet defaultAttributeValueSet = attributeSets .getDefaultAttributeValueSet(); if (defaultAttributeValueSet != null) { initAttributes.putAll(defaultAttributeValueSet.getAttributeValues()); } final ControllerContextImpl context = new ControllerContextImpl(); try { AttributeBinder .set(instance, initAttributes, false, Input.class); try { instance.init(context); } catch (Throwable t) { // Ignore if failed to initialize. } AttributeBinder.get(instance, initAttributes, Output.class, Init.class); } finally { context.dispose(); } return instance; } /** * Builds and returns a {@link BindableDescriptor} for an instance of this * descriptor's {@link IProcessingComponent}, with default {@link Init} attributes * initialized with the default attribute set. If the default attribute set does not * provide values for some required {@link Bindable} {@link Init} attributes, the * returned descriptor may be incomplete. */ public BindableDescriptor getBindableDescriptor() { if (bindableDescriptor == null) throw new RuntimeException( "Descriptor not available.", this.initializationException); return bindableDescriptor; } /** * @return Return <code>true</code> if instances of this descriptor are available * (class can be resolved, instances can be created). */ public boolean isComponentAvailable() { return this.initializationException == null; } /** * Returns initialization failure ({@link Throwable}) or <code>null</code>. */ public Throwable getInitializationFailure() { return this.initializationException; } /** * Invoked by the XML loading framework when the object is deserialized. */ private void loadAttributeSets(ResourceLookup resourceLookup) throws Exception { attributeSets = new AttributeValueSets(); IResource resource = null; if (!StringUtils.isBlank(attributeSetsResource)) { // Try to load from the directly provided resource name resource = resourceLookup.getFirst(attributeSetsResource); if (resource == null) { throw new IOException("Attribute set resource not found: " + attributeSetsResource); } } if (resource != null) { final InputStream inputStream = resource.open(); try { attributeSets = AttributeValueSets.deserialize(inputStream); } finally { CloseableUtils.close(inputStream); } } if (getAttributeSets() == null) { attributeSets = new AttributeValueSets(); } } /** * On commit, attempt to verify component class and instance availability. */ @Commit private void onCommit(Map<Object, Object> session) { this.initializationException = null; try { ResourceLookup resourceLookup = PersisterHelpers.getResourceLookup(session); loadAttributeSets(resourceLookup); bindableDescriptor = BindableDescriptorBuilder.buildDescriptor(newInitializedInstance()); } catch (Throwable e) { org.slf4j.LoggerFactory.getLogger(this.getClass()).warn( "Component unavailable: " + componentClassName, e); this.initializationException = e; } } /** * Transforms a {@link ProcessingComponentDescriptor} to its identifier. */ public static final class ProcessingComponentDescriptorToId implements Function<ProcessingComponentDescriptor, String> { public static final ProcessingComponentDescriptorToId INSTANCE = new ProcessingComponentDescriptorToId(); private ProcessingComponentDescriptorToId() { } public String apply(ProcessingComponentDescriptor descriptor) { return descriptor.id; } } @Override public String toString() { return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE); } }