package org.springframework.roo.addon.solr; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.List; import javax.xml.parsers.DocumentBuilder; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.Validate; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Reference; import org.apache.felix.scr.annotations.Service; import org.osgi.service.component.ComponentContext; import org.springframework.roo.addon.jpa.activerecord.JpaActiveRecordMetadata; import org.springframework.roo.addon.web.mvc.controller.scaffold.WebScaffoldMetadata; import org.springframework.roo.addon.web.mvc.jsp.menu.MenuOperations; import org.springframework.roo.addon.web.mvc.jsp.roundtrip.XmlRoundTripFileManager; import org.springframework.roo.addon.web.mvc.jsp.tiles.TilesOperations; import org.springframework.roo.classpath.TypeLocationService; import org.springframework.roo.classpath.details.BeanInfoUtils; import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetails; import org.springframework.roo.classpath.details.FieldMetadata; import org.springframework.roo.classpath.details.MethodMetadata; import org.springframework.roo.classpath.persistence.PersistenceMemberLocator; import org.springframework.roo.classpath.scanner.MemberDetails; import org.springframework.roo.classpath.scanner.MemberDetailsScanner; import org.springframework.roo.metadata.MetadataDependencyRegistry; import org.springframework.roo.metadata.MetadataIdentificationUtils; import org.springframework.roo.metadata.MetadataItem; import org.springframework.roo.metadata.MetadataNotificationListener; import org.springframework.roo.metadata.MetadataProvider; import org.springframework.roo.metadata.MetadataService; import org.springframework.roo.model.JavaSymbolName; import org.springframework.roo.model.JavaType; import org.springframework.roo.process.manager.FileManager; import org.springframework.roo.project.LogicalPath; import org.springframework.roo.project.Path; import org.springframework.roo.project.PathResolver; import org.springframework.roo.support.util.FileUtils; import org.springframework.roo.support.util.XmlElementBuilder; import org.springframework.roo.support.util.XmlRoundTripUtils; import org.springframework.roo.support.util.XmlUtils; import org.w3c.dom.Document; import org.w3c.dom.Element; /** * Metadata listener responsible for installing Web MVC JSP artifacts for the * Solr search addon. * * @author Stefan Schmidt * @since 1.1 */ @Component(immediate = true) @Service public class SolrJspMetadataListener implements MetadataProvider, MetadataNotificationListener { @Reference private FileManager fileManager; private JavaType formbackingObject; private JavaType javaType; private JpaActiveRecordMetadata jpaActiveRecordMetadata; @Reference private MemberDetailsScanner memberDetailsScanner; @Reference private MenuOperations menuOperations; @Reference private MetadataDependencyRegistry metadataDependencyRegistry; @Reference private MetadataService metadataService; @Reference private PathResolver pathResolver; @Reference private PersistenceMemberLocator persistenceMemberLocator; @Reference private TilesOperations tilesOperations; @Reference private TypeLocationService typeLocationService; private WebScaffoldMetadata webScaffoldMetadata; @Reference private XmlRoundTripFileManager xmlRoundTripFileManager; protected void activate(final ComponentContext context) { metadataDependencyRegistry.registerDependency( SolrWebSearchMetadata.getMetadataIdentiferType(), getProvidesType()); } private void copyArtifacts(final String relativeTemplateLocation, final String relativeProjectFileLocation) { // First install search.tagx final String projectFileLocation = pathResolver.getFocusedIdentifier( Path.SRC_MAIN_WEBAPP, relativeProjectFileLocation); if (!fileManager.exists(projectFileLocation)) { InputStream inputStream = null; OutputStream outputStream = null; try { inputStream = FileUtils.getInputStream(getClass(), relativeTemplateLocation); outputStream = fileManager.createFile(projectFileLocation) .getOutputStream(); IOUtils.copy(inputStream, outputStream); } catch (final IOException e) { throw new IllegalStateException("Could not copy " + relativeProjectFileLocation + " into project", e); } finally { IOUtils.closeQuietly(inputStream); IOUtils.closeQuietly(outputStream); } } } public MetadataItem get(final String metadataIdentificationString) { javaType = SolrJspMetadata.getJavaType(metadataIdentificationString); final LogicalPath path = SolrJspMetadata .getPath(metadataIdentificationString); final String solrWebSearchMetadataKeyString = SolrWebSearchMetadata .createIdentifier(javaType, path); final SolrWebSearchMetadata webSearchMetadata = (SolrWebSearchMetadata) metadataService .get(solrWebSearchMetadataKeyString); if (webSearchMetadata == null || !webSearchMetadata.isValid()) { return null; } webScaffoldMetadata = (WebScaffoldMetadata) metadataService .get(WebScaffoldMetadata.createIdentifier(javaType, path)); Validate.notNull(webScaffoldMetadata, "Web scaffold metadata required"); formbackingObject = webScaffoldMetadata.getAnnotationValues() .getFormBackingObject(); jpaActiveRecordMetadata = (JpaActiveRecordMetadata) metadataService .get(JpaActiveRecordMetadata.createIdentifier( formbackingObject, path)); Validate.notNull( jpaActiveRecordMetadata, "Could not determine entity metadata for type: " + javaType.getFullyQualifiedTypeName()); installMvcArtifacts(webScaffoldMetadata); return new SolrJspMetadata(metadataIdentificationString, webSearchMetadata); } public String getProvidesType() { return SolrJspMetadata.getMetadataIdentiferType(); } private Document getSearchDocument( final WebScaffoldMetadata webScaffoldMetadata) { // Next install search.jspx Validate.notNull(webScaffoldMetadata, "Web scaffold metadata required"); final DocumentBuilder builder = XmlUtils.getDocumentBuilder(); final Document document = builder.newDocument(); // Add document namespaces final Element div = new XmlElementBuilder("div", document) .addAttribute("xmlns:page", "urn:jsptagdir:/WEB-INF/tags/form") .addAttribute("xmlns:fields", "urn:jsptagdir:/WEB-INF/tags/form/fields") .addAttribute("xmlns:jsp", "http://java.sun.com/JSP/Page") .addAttribute("version", "2.0") .addChild( new XmlElementBuilder("jsp:output", document) .addAttribute("omit-xml-declaration", "yes") .build()).build(); document.appendChild(div); final Element pageSearch = new XmlElementBuilder("page:search", document) .addAttribute( "id", XmlUtils.convertId("ps:" + webScaffoldMetadata.getAnnotationValues() .getFormBackingObject() .getFullyQualifiedTypeName())) .addAttribute("path", webScaffoldMetadata.getAnnotationValues().getPath()) .build(); pageSearch.setAttribute("z", XmlRoundTripUtils.calculateUniqueKeyFor(pageSearch)); final List<FieldMetadata> idFields = persistenceMemberLocator .getIdentifierFields(formbackingObject); if (idFields.isEmpty()) { return null; } final Element resultTable = new XmlElementBuilder("fields:table", document) .addAttribute( "id", XmlUtils.convertId("rt:" + webScaffoldMetadata.getAnnotationValues() .getFormBackingObject() .getFullyQualifiedTypeName())) .addAttribute("data", "${searchResults}") .addAttribute("delete", "false") .addAttribute("update", "false") .addAttribute("path", webScaffoldMetadata.getAnnotationValues().getPath()) .addAttribute( "typeIdFieldName", formbackingObject.getSimpleTypeName().toLowerCase() + "." + idFields.get(0).getFieldName() .getSymbolName().toLowerCase() + SolrUtils.getSolrDynamicFieldPostFix(idFields .get(0).getFieldType())).build(); resultTable.setAttribute("z", XmlRoundTripUtils.calculateUniqueKeyFor(resultTable)); final StringBuilder facetFields = new StringBuilder(); int fieldCounter = 0; final ClassOrInterfaceTypeDetails formbackingClassOrInterfaceDetails = typeLocationService .getTypeDetails(formbackingObject); Validate.notNull(formbackingClassOrInterfaceDetails, "Unable to obtain physical type metadata for type " + formbackingObject.getFullyQualifiedTypeName()); final MemberDetails memberDetails = memberDetailsScanner .getMemberDetails(getClass().getName(), formbackingClassOrInterfaceDetails); final MethodMetadata identifierAccessor = persistenceMemberLocator .getIdentifierAccessor(formbackingObject); final MethodMetadata versionAccessor = persistenceMemberLocator .getVersionAccessor(formbackingObject); for (final MethodMetadata method : memberDetails.getMethods()) { // Only interested in accessors if (!BeanInfoUtils.isAccessorMethod(method)) { continue; } if (++fieldCounter < 7) { if (method.getMethodName().equals( identifierAccessor.getMethodName()) || method.getMethodName().equals( versionAccessor.getMethodName())) { continue; } if (method.hasSameName(identifierAccessor, versionAccessor)) { continue; } final FieldMetadata field = BeanInfoUtils .getFieldForJavaBeanMethod(memberDetails, method); if (field == null) { continue; } facetFields .append(formbackingObject.getSimpleTypeName() .toLowerCase()) .append(".") .append(field.getFieldName()) .append(SolrUtils.getSolrDynamicFieldPostFix(field .getFieldType())).append(","); final Element columnElement = new XmlElementBuilder( "fields:column", document) .addAttribute( "id", XmlUtils.convertId("c:" + formbackingObject .getFullyQualifiedTypeName() + "." + field.getFieldName().getSymbolName())) .addAttribute( "property", formbackingObject.getSimpleTypeName() .toLowerCase() + "." + field.getFieldName().getSymbolName() .toLowerCase() + SolrUtils .getSolrDynamicFieldPostFix(field .getFieldType())) .build(); columnElement.setAttribute("z", XmlRoundTripUtils.calculateUniqueKeyFor(columnElement)); resultTable.appendChild(columnElement); } } final Element searchFacet = new XmlElementBuilder( "fields:search-facet", document) .addAttribute( "id", XmlUtils.convertId("sfacet:" + webScaffoldMetadata.getAnnotationValues() .getFormBackingObject() .getFullyQualifiedTypeName())) .addAttribute("facetFields", facetFields.toString()).build(); searchFacet.setAttribute("z", XmlRoundTripUtils.calculateUniqueKeyFor(searchFacet)); pageSearch.appendChild(searchFacet); final Element searchField = new XmlElementBuilder( "fields:search-field", document).addAttribute( "id", XmlUtils.convertId("sfield:" + webScaffoldMetadata.getAnnotationValues() .getFormBackingObject() .getFullyQualifiedTypeName())).build(); searchField.setAttribute("z", XmlRoundTripUtils.calculateUniqueKeyFor(searchField)); pageSearch.appendChild(searchFacet); pageSearch.appendChild(searchField); pageSearch.appendChild(resultTable); div.appendChild(pageSearch); return document; } public void installMvcArtifacts( final WebScaffoldMetadata webScaffoldMetadata) { copyArtifacts("form/search.tagx", "WEB-INF/tags/form/search.tagx"); copyArtifacts("form/fields/search-facet.tagx", "WEB-INF/tags/form/fields/search-facet.tagx"); copyArtifacts("form/fields/search-field.tagx", "WEB-INF/tags/form/fields/search-field.tagx"); final LogicalPath path = WebScaffoldMetadata .getPath(webScaffoldMetadata.getId()); xmlRoundTripFileManager.writeToDiskIfNecessary(pathResolver .getIdentifier( Path.SRC_MAIN_WEBAPP.getModulePathId(path.getModule()), "WEB-INF/views/" + webScaffoldMetadata.getAnnotationValues() .getPath() + "/search.jspx"), getSearchDocument(webScaffoldMetadata)); final String folderName = webScaffoldMetadata.getAnnotationValues() .getPath(); tilesOperations.addViewDefinition(folderName, path, folderName + "/search", TilesOperations.DEFAULT_TEMPLATE, "WEB-INF/views/" + webScaffoldMetadata.getAnnotationValues().getPath() + "/search.jspx"); menuOperations.addMenuItem( new JavaSymbolName(formbackingObject.getSimpleTypeName()), new JavaSymbolName("solr"), new JavaSymbolName( jpaActiveRecordMetadata.getPlural()) .getReadableSymbolName(), "global.menu.find", "/" + webScaffoldMetadata.getAnnotationValues().getPath() + "?search", "s:", path); } public void notify(final String upstreamDependency, String downstreamDependency) { if (MetadataIdentificationUtils .isIdentifyingClass(downstreamDependency)) { Validate.isTrue( MetadataIdentificationUtils.getMetadataClass( upstreamDependency).equals( MetadataIdentificationUtils .getMetadataClass(SolrWebSearchMetadata .getMetadataIdentiferType())), "Expected class-level notifications only for Solr web search metadata (not '" + upstreamDependency + "')"); // A physical Java type has changed, and determine what the // corresponding local metadata identification string would have // been final JavaType javaType = SolrWebSearchMetadata .getJavaType(upstreamDependency); final LogicalPath path = SolrWebSearchMetadata .getPath(upstreamDependency); downstreamDependency = SolrJspMetadata.createIdentifier(javaType, path); // We only need to proceed if the downstream dependency relationship // is not already registered // (if it's already registered, the event will be delivered directly // later on) if (metadataDependencyRegistry.getDownstream(upstreamDependency) .contains(downstreamDependency)) { return; } } // We should now have an instance-specific "downstream dependency" that // can be processed by this class Validate.isTrue( MetadataIdentificationUtils.getMetadataClass( downstreamDependency).equals( MetadataIdentificationUtils .getMetadataClass(getProvidesType())), "Unexpected downstream notification for '" + downstreamDependency + "' to this provider (which uses '" + getProvidesType() + "'"); metadataService.evict(downstreamDependency); if (get(downstreamDependency) != null) { metadataDependencyRegistry.notifyDownstream(downstreamDependency); } } }