Java tutorial
/** * Copyright 2011 The Apache Software Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * @author Felipe Oliveira (http://mashup.fm) * */ package elasticsearch; import static org.elasticsearch.node.NodeBuilder.nodeBuilder; import java.util.Collections; import java.util.Enumeration; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.lang.Validate; import org.elasticsearch.client.Client; import org.elasticsearch.client.transport.TransportClient; import org.elasticsearch.common.settings.ImmutableSettings; import org.elasticsearch.common.settings.ImmutableSettings.Builder; import org.elasticsearch.common.transport.InetSocketTransportAddress; import org.elasticsearch.node.Node; import org.elasticsearch.node.NodeBuilder; import play.Logger; import play.Play; import play.PlayPlugin; import play.db.Model; import elasticsearch.ElasticSearchIndexEvent.Type; import elasticsearch.adapter.ElasticSearchAdapter; import elasticsearch.annotations.ElasticSearchable; import elasticsearch.mapping.MapperFactory; import elasticsearch.mapping.MappingException; import elasticsearch.mapping.MappingUtil; import elasticsearch.mapping.ModelMapper; import elasticsearch.mapping.impl.DefaultMapperFactory; import elasticsearch.util.ExceptionUtil; import elasticsearch.util.ReflectionUtil; import play.mvc.Router; // TODO: Auto-generated Javadoc /** * The Class ElasticSearchPlugin. */ public class ElasticSearchPlugin extends PlayPlugin { /** The started. */ private static boolean started = false; /** The mapper factory */ private static MapperFactory mapperFactory = new DefaultMapperFactory(); private static volatile ElasticSearchDeliveryMode currentDeliveryMode; /** The mappers index. */ private static Map<Class<?>, ModelMapper<?>> mappers = null; /** The started indices. */ private static Set<Class<?>> indicesStarted = null; /** Index type -> Class lookup */ private static Map<String, Class<?>> modelLookup = null; /** The client. */ private static Client client = null; /** * Client. * * @return the client */ public static Client client() { return client; } public static void setMapperFactory(final MapperFactory factory) { mapperFactory = factory; mappers.clear(); } /** * Checks if is local mode. * * @return true, if is local mode */ private boolean isLocalMode() { try { final String client = Play.configuration.getProperty("elasticsearch.client"); final Boolean local = Boolean.getBoolean(Play.configuration.getProperty("elasticsearch.local", "true")); if (client == null) { return true; } if (client.equalsIgnoreCase("false") || client.equalsIgnoreCase("true")) { return true; } return local; } catch (final Exception e) { Logger.error("Error! Starting in Local Model: %s", ExceptionUtil.getStackTrace(e)); return true; } } /** * Gets the hosts. * * @return the hosts */ public static String getHosts() { final String s = Play.configuration.getProperty("elasticsearch.client"); if (s == null) { return ""; } return s; } public static ElasticSearchDeliveryMode getDeliveryMode() { return currentDeliveryMode; } public static void setDeliveryMode(final ElasticSearchDeliveryMode deliveryMode) { currentDeliveryMode = deliveryMode; } /** * Gets the delivery mode from the configuration. * * @return the delivery mode */ public static ElasticSearchDeliveryMode getDeliveryModeFromConfiguration() { final String s = Play.configuration.getProperty("elasticsearch.delivery"); if (s == null) { return ElasticSearchDeliveryMode.LOCAL; } if ("CUSTOM".equals(s)) return ElasticSearchDeliveryMode.createCustomIndexEventHandler(Play.configuration.getProperty( "elasticsearch.customIndexEventHandler", "play.modules.elasticsearch.LocalIndexEventHandler")); return ElasticSearchDeliveryMode.valueOf(s.toUpperCase()); } /** * This method is called when the application starts - It will start ES instance * * @see play.PlayPlugin#onApplicationStart() */ @Override public void onApplicationStart() { // (re-)set caches mappers = new ConcurrentHashMap<Class<?>, ModelMapper<?>>(); modelLookup = new ConcurrentHashMap<String, Class<?>>(); indicesStarted = Collections.newSetFromMap(new ConcurrentHashMap<Class<?>, Boolean>()); ReflectionUtil.clearCache(); // Make sure it doesn't get started more than once if ((client != null) || started) { Logger.debug("Elastic Search Started Already!"); return; } // Start Node Builder final Builder settings = ImmutableSettings.settingsBuilder(); // settings.put("client.transport.sniff", true); // Import anything from play configuration that starts with elasticsearch.native. final Enumeration<Object> keys = Play.configuration.keys(); while (keys.hasMoreElements()) { final String key = (String) keys.nextElement(); if (key.startsWith("elasticsearch.native.")) { final String nativeKey = key.replaceFirst("elasticsearch.native.", ""); Logger.error("Adding native [" + nativeKey + "," + Play.configuration.getProperty(key) + "]"); settings.put(nativeKey, Play.configuration.getProperty(key)); } } settings.build(); // Check Model if (this.isLocalMode()) { Logger.info("Starting Elastic Search for Play! in Local Mode"); final NodeBuilder nb = nodeBuilder().settings(settings).local(true).client(false).data(true); final Node node = nb.node(); client = node.client(); } else { Logger.info("Connecting Play! to Elastic Search in Client Mode"); final TransportClient c = new TransportClient(settings); if (Play.configuration.getProperty("elasticsearch.client") == null) { throw new RuntimeException( "Configuration required - elasticsearch.client when local model is disabled!"); } final String[] hosts = getHosts().trim().split(","); boolean done = false; for (final String host : hosts) { final String[] parts = host.split(":"); if (parts.length != 2) { throw new RuntimeException("Invalid Host: " + host); } Logger.info("Transport Client - Host: %s Port: %s", parts[0], parts[1]); if (Integer.valueOf(parts[1]) == 9200) Logger.info( "Note: Port 9200 is usually used by the HTTP Transport. You might want to use 9300 instead."); c.addTransportAddress(new InetSocketTransportAddress(parts[0], Integer.valueOf(parts[1]))); done = true; } if (done == false) { throw new RuntimeException("No Hosts Provided for Elastic Search!"); } client = c; } // Configure current delivery mode setDeliveryMode(getDeliveryModeFromConfiguration()); // Bind Admin Router.addRoute("GET", "/es-admin", "elasticsearch.ElasticSearchAdmin.index"); // Check Client if (client == null) { throw new RuntimeException( "Elastic Search Client cannot be null - please check the configuration provided and the health of your Elastic Search instances."); } } @SuppressWarnings("unchecked") public static <M> ModelMapper<M> getMapper(final Class<M> clazz) { if (mappers.containsKey(clazz)) { return (ModelMapper<M>) mappers.get(clazz); } final ModelMapper<M> mapper = mapperFactory.getMapper(clazz); mappers.put(clazz, mapper); modelLookup.put(mapper.getTypeName(), clazz); return mapper; } private static void startIndexIfNeeded(final Class<Model> clazz) { if (!indicesStarted.contains(clazz)) { final ModelMapper<Model> mapper = getMapper(clazz); Logger.info("Start Index for Class: %s", clazz); ElasticSearchAdapter.startIndex(client(), mapper); indicesStarted.add(clazz); } } private static boolean isInterestingEvent(final String event) { return event.endsWith(".objectPersisted") || event.endsWith(".objectUpdated") || event.endsWith(".objectDeleted"); } /** * This is the method that will be sending data to ES instance * * @see play.PlayPlugin#onEvent(java.lang.String, java.lang.Object) */ @Override public void onEvent(final String message, final Object context) { // Log Debug Logger.info("Received %s Event, Object: %s", message, context); if (isInterestingEvent(message) == false) { return; } Logger.debug("Processing %s Event", message); // Check if object is searchable if (MappingUtil.isSearchable(context.getClass()) == false) { return; } // Sanity check, we only index models Validate.isTrue(context instanceof Model, "Only play.db.Model subclasses can be indexed"); // Start index if needed @SuppressWarnings("unchecked") final Class<Model> clazz = (Class<Model>) context.getClass(); startIndexIfNeeded(clazz); // Define Event ElasticSearchIndexEvent event = null; if (message.endsWith(".objectPersisted") || message.endsWith(".objectUpdated")) { // Index Model event = new ElasticSearchIndexEvent((Model) context, ElasticSearchIndexEvent.Type.INDEX); } else if (message.endsWith(".objectDeleted")) { // Delete Model from Index event = new ElasticSearchIndexEvent((Model) context, ElasticSearchIndexEvent.Type.DELETE); } // Sync with Elastic Search Logger.info("Elastic Search Index Event: %s", event); if (event != null) { final ElasticSearchDeliveryMode deliveryMode = getDeliveryMode(); final IndexEventHandler handler = deliveryMode.getHandler(); handler.handle(event); } } <M extends Model> void index(final M model) { final ElasticSearchDeliveryMode deliveryMode = getDeliveryMode(); index(model, deliveryMode); } public <M extends Model> void index(final M model, final ElasticSearchDeliveryMode deliveryMode) { @SuppressWarnings("unchecked") final Class<Model> clazz = (Class<Model>) model.getClass(); // Check if object is searchable if (MappingUtil.isSearchable(clazz) == false) { throw new IllegalArgumentException("model is not searchable"); } startIndexIfNeeded(clazz); final ElasticSearchIndexEvent event = new ElasticSearchIndexEvent(model, Type.INDEX); final IndexEventHandler handler = deliveryMode.getHandler(); handler.handle(event); } /** * Looks up the model class based on the index type name * * @param indexType * @return Class of the Model */ public static Class<?> lookupModel(final String indexType) { final Class<?> clazz = modelLookup.get(indexType); if (clazz != null) { // we have not cached this model yet return clazz; } final List<Class> searchableModels = Play.classloader.getAnnotatedClasses(ElasticSearchable.class); for (final Class searchableModel : searchableModels) { try { if (indexType.equals(getMapper(searchableModel).getTypeName())) { return searchableModel; } } catch (final MappingException ex) { // mapper can not be retrieved } } throw new IllegalArgumentException("Type name '" + indexType + "' is not searchable!"); } }