Mercurial > dive4elements > river
view flys-artifacts/src/main/java/de/intevation/flys/collections/FLYSArtifactCollection.java @ 2590:d75b427da50a
#522 Modified axes creation: label and tick label fonts will now always be the same.
flys-artifacts/trunk@4146 c6561f87-3c4e-4783-a992-168aeb5c3f6f
author | Ingo Weinzierl <ingo.weinzierl@intevation.de> |
---|---|
date | Thu, 15 Mar 2012 12:28:11 +0000 |
parents | a71fc8f2030c |
children | 226c360febae 804088c2e23a |
line wrap: on
line source
package de.intevation.flys.collections; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import javax.xml.xpath.XPathConstants; import org.apache.log4j.Logger; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import de.intevation.artifacts.Artifact; import de.intevation.artifacts.ArtifactDatabase; import de.intevation.artifacts.ArtifactDatabaseException; import de.intevation.artifacts.ArtifactNamespaceContext; import de.intevation.artifacts.CallContext; import de.intevation.artifacts.CallMeta; import de.intevation.artifacts.common.utils.ClientProtocolUtils; import de.intevation.artifacts.common.utils.XMLUtils; import de.intevation.artifactdatabase.Backend; import de.intevation.artifactdatabase.Backend.PersistentArtifact; import de.intevation.artifactdatabase.DefaultArtifactCollection; import de.intevation.artifactdatabase.state.ArtifactAndFacet; import de.intevation.artifactdatabase.state.Output; import de.intevation.artifactdatabase.state.Settings; import de.intevation.artifactdatabase.state.StateEngine; import de.intevation.flys.artifacts.context.FLYSContext; import de.intevation.flys.artifacts.FLYSArtifact; import de.intevation.flys.artifacts.model.ManagedFacet; import de.intevation.flys.artifacts.model.ManagedDomFacet; import de.intevation.flys.exports.OutGenerator; import de.intevation.flys.themes.Theme; import de.intevation.flys.themes.ThemeFactory; import de.intevation.flys.utils.FLYSUtils; /** * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a> */ public class FLYSArtifactCollection extends DefaultArtifactCollection { /** The logger used in this class. */ private static Logger log = Logger.getLogger(FLYSArtifactCollection.class); /** Constant XPath that points to the outputmodes of an artifact. */ public static final String XPATH_ARTIFACT_OUTPUTMODES = "/art:result/art:outputmodes"; public static final String XPATH_ARTIFACT_STATE_DATA = "/art:result/art:ui/art:static/art:state/art:data"; public static final String XPATH_COLLECTION_ITEMS = "/art:result/art:artifact-collection/art:collection-item"; public static final String XPATH_OUT_NAME = "/art:action/@art:name"; public static final String XPATH_OUT_TYPE = "/art:action/@art:type"; /** Xpath to master artifacts uuid. */ public static final String XPATH_MASTER_UUID = "/art:artifact-collection/art:artifact/@art:uuid"; public static final String XPATH_LOADED_RECOMMENDATIONS = "/art:attribute/art:loaded-recommendations"; /** * Return description Document for this collection. */ @Override public Document describe(CallContext context) { log.debug("FLYSArtifactCollection.describe: " + identifier); CollectionDescriptionHelper helper = new CollectionDescriptionHelper( getName(), identifier(), getCreationTime(), getTTL(), context); ArtifactDatabase db = context.getDatabase(); Document oldAttrs = getAttribute(); AttributeParser parser = new AttributeParser(oldAttrs); try { String[] aUUIDs = getArtifactUUIDs(context); oldAttrs = removeAttributes(oldAttrs, context); parser = new AttributeParser(oldAttrs); CollectionAttribute newAttr = mergeAttributes( db, context, parser, aUUIDs); if (checkOutputSettings(newAttr, context)) { saveCollectionAttribute(db, context, newAttr); } helper.setAttribute(newAttr); // Make it an empty array if null. if (aUUIDs == null) { aUUIDs = new String[] {}; } for (String uuid: aUUIDs) { helper.addArtifact(uuid); } } catch (ArtifactDatabaseException ade) { log.error("Error while merging attribute documents.", ade); helper.setAttribute(parser.getCollectionAttribute()); } return helper.toXML(); } /** * Merge the current art:outputs nodes with the the outputs provided by the * artifacts in the Collection. * * @param uuids Artifact uuids. */ protected CollectionAttribute mergeAttributes( ArtifactDatabase db, CallContext context, AttributeParser oldParser, String[] uuids ) { CollectionAttribute cAttribute = buildOutAttributes(db, context, oldParser, uuids); cAttribute.setLoadedRecommendations( getLoadedRecommendations(oldParser.getAttributeDocument())); saveCollectionAttribute(db, context, cAttribute); return cAttribute; } protected Document removeAttributes(Document attrs, CallContext context) { FLYSArtifact master = getMasterArtifact(context); List<Output> outList = master.getOutputs(context); Document doc = XMLUtils.newDocument(); XMLUtils.ElementCreator ec = new XMLUtils.ElementCreator( doc, ArtifactNamespaceContext.NAMESPACE_URI, ArtifactNamespaceContext.NAMESPACE_PREFIX); Element root = ec.create("attribute"); doc.appendChild(root); if (outList.size() > 0) { Element outs = ec.create("outputs"); root.appendChild(outs); for (Output o : outList) { Node n = (Node) XMLUtils.xpath( attrs, "/art:attribute/art:outputs/art:output[@name='" + o.getName() + "']", XPathConstants.NODE, ArtifactNamespaceContext.INSTANCE); if (n != null) { Node clone = doc.importNode(n, true); outs.appendChild(clone); } } } return doc; } /** * @param db The ArtifactDatabase which is required to save the attribute * into. * @param attribute The CollectionAttribute that should be stored in the * database. * * @return true, if the transaction was successful, otherwise false. */ protected boolean saveCollectionAttribute( ArtifactDatabase db, CallContext context, CollectionAttribute attribute ) { log.info("Save new CollectionAttribute into database."); Document doc = attribute.toXML(); try { // Save the merged document into database. db.setCollectionAttribute(identifier(), context.getMeta(), doc); log.info("Saving CollectionAttribute was successful."); return true; } catch (ArtifactDatabaseException adb) { log.error(adb, adb); } return false; } /** * Merge the recommendations which have already been loaded from the old * attribute document into the new attribute document. This is necessary, * because mergeAttributes() only merges the art:outputs nodes - all * other nodes are skiped. */ protected Node getLoadedRecommendations(Document oldAttrs) { Element loadedRecoms = (Element) XMLUtils.xpath( oldAttrs, XPATH_LOADED_RECOMMENDATIONS, XPathConstants.NODE, ArtifactNamespaceContext.INSTANCE); return loadedRecoms; } /** * Evaluates the Output settings. If an Output has no Settings set, the * relevant OutGenerator is used to initialize a default Settings object. * * @param attribute The CollectionAttribute. * @param cc The CallContext. * * @return true, if the CollectionAttribute was modified, otherwise false. */ protected boolean checkOutputSettings( CollectionAttribute attribute, CallContext cc ) { boolean modified = false; Map<String, Output> outputMap = attribute != null ? attribute.getOutputs() : null; if (outputMap == null || outputMap.size() == 0) { log.debug("No Output Settings check necessary."); return modified; } Set<Map.Entry<String, Output>> entries = outputMap.entrySet(); for (Map.Entry<String, Output> entry: entries) { String outName = entry.getKey(); Output output = entry.getValue(); Settings settings = output.getSettings(); if (settings == null) { log.debug("No Settings set for Output '" + outName + "'."); output.setSettings( createInitialOutputSettings(cc, attribute, outName)); modified = true; } } return modified; } /** * This method uses the the OutGenerator for the specified Output * <i>out</i> to create an initial Settings object. * * @param cc The CallContext object. * @param attr The CollectionAttribute. * @param out The name of the output. * * @return a default Settings object for the specified Output. */ protected Settings createInitialOutputSettings( CallContext cc, CollectionAttribute attr, String out ) { OutGenerator outGen = getOutGenerator(cc, out, null); if (outGen == null) { return null; } // XXX NOTE: the outGen is not able to process its generate() operation, // because it has no OutputStream set! outGen.init(XMLUtils.newDocument(), null, cc); prepareMasterArtifact(outGen, cc); try { Document outAttr = getAttribute(cc, attr, out); doOut(outGen, out, out, outAttr, cc); } catch (ArtifactDatabaseException adbe) { log.error(adbe, adbe); } catch (IOException ioe) { log.error(ioe, ioe); } return outGen.getSettings(); } @Override public void out( String type, Document format, OutputStream out, CallContext context) throws IOException { long reqBegin = System.currentTimeMillis(); log.info("FLYSArtifactCollection.out"); String name = XMLUtils.xpathString( format, XPATH_OUT_NAME, ArtifactNamespaceContext.INSTANCE); String subtype = XMLUtils.xpathString( format, XPATH_OUT_TYPE, ArtifactNamespaceContext.INSTANCE); log.info("-> Output name = " + name); log.info("-> Output type = " + type); log.info("-> Output subtype = " + subtype); OutGenerator generator = null; if (type != null && type.length() > 0 && type.indexOf("chartinfo") > 0) { generator = getOutGenerator(context, type, subtype); } else { generator = getOutGenerator(context, name, subtype); } if (generator == null) { log.error("There is no generator specified for output: " + name); // TODO Throw an exception. return; } Document oldAttrs = getAttribute(); AttributeParser parser = new AttributeParser(oldAttrs); CollectionAttribute cAttr = parser.getCollectionAttribute(); Output output = cAttr.getOutput(name); Settings settings = output.getSettings(); generator.init(format, out, context); generator.setSettings(settings); prepareMasterArtifact(generator, context); try { Document attr = getAttribute(context, cAttr, name); doOut(generator, name, subtype, attr, context); generator.generate(); } catch (ArtifactDatabaseException adbe) { log.error(adbe, adbe); } long duration = System.currentTimeMillis() -reqBegin; log.info("Processing out(" + name + ") took " + duration + " ms."); } /** * Sets the master Artifact at the given <i>generator</i>. * * @param generator The generator that gets a master Artifact. * @param cc The CallContext. */ protected void prepareMasterArtifact(OutGenerator generator, CallContext cc ) { // Get master artifact. FLYSArtifact master = getMasterArtifact(cc); if (master != null) { log.debug("Set master Artifact to uuid: " + master.identifier()); generator.setMasterArtifact(master); } else { log.warn("Could not set master artifact."); } } /** * Creates the concrete output. * * @param generator The OutGenerator that creates the output. * @param outputName The name of the requested output. * @param attributes The collection's attributes for this concrete output * type. * @param context The context object. */ protected void doOut( OutGenerator generator, String outName, String facet, Document attributes, CallContext context) throws IOException { log.info("FLYSArtifactCollection.doOut: " + outName); ThemeList themeList = new ThemeList(attributes); int size = themeList.size(); log.debug("Output will contain " + size + " elements."); List<ArtifactAndFacet> dataProviders = doBlackboardPass(themeList, context); try { for (int i = 0; i < size; i++) { ManagedFacet theme = themeList.get(i); if (theme == null) { log.debug("Theme is empty - no output is generated."); continue; } String art = theme.getArtifact(); String facetName = theme.getName(); if (log.isDebugEnabled()) { log.debug("Do output for..."); log.debug("... artifact: " + art); log.debug("... facet: " + facetName); } if (outName.equals("export") && !facetName.equals(facet)) { continue; } // Skip invisible themes. if (theme.getVisible() == 0) { continue; } generator.doOut( dataProviders.get(i), getFacetThemeFromAttribute( art, outName, facetName, theme.getDescription(), theme.getIndex(), context), theme.getActive() == 1); } } catch (ArtifactDatabaseException ade) { log.error(ade, ade); } } /** * Show blackboard (context) to each facet and create a list of * ArtifactAndFacets on the fly (with the same ordering as the passed * ThemeList). * @param themeList ThemeList to create a ArtifactAndFacetList along. * @param contect The "Blackboard". */ protected List<ArtifactAndFacet> doBlackboardPass( ThemeList themeList, CallContext context ) { ArrayList<ArtifactAndFacet> dataProviders = new ArrayList<ArtifactAndFacet>(); int size = themeList.size(); try { // Collect all ArtifactAndFacets for blackboard pass. for (int i = 0; i < size; i++) { ManagedFacet theme = themeList.get(i); if (theme == null) { log.warn("A ManagedFacet in ThemeList is null."); continue; } String uuid = theme.getArtifact(); Artifact artifact = getArtifact(uuid, context); FLYSArtifact flys = (FLYSArtifact) artifact; ArtifactAndFacet artifactAndFacet = new ArtifactAndFacet( artifact, flys.getNativeFacet(theme)); // XXX HELP ME PLEASE artifactAndFacet.setFacetDescription(theme.getDescription()); // Show blackboard to facet. artifactAndFacet.register(context); // Add to themes. dataProviders.add(i, artifactAndFacet); } } catch (ArtifactDatabaseException ade) { log.error("ArtifactDatabaseException!", ade); } return dataProviders; } /** * @return masterartifact or null if exception/not found. */ protected FLYSArtifact getMasterArtifact(CallContext context) { try { ArtifactDatabase db = context.getDatabase(); CallMeta callMeta = context.getMeta(); Document document = db.getCollectionsMasterArtifact( identifier(), callMeta); String masterUUID = XMLUtils.xpathString( document, XPATH_MASTER_UUID, ArtifactNamespaceContext.INSTANCE); FLYSArtifact masterArtifact = (FLYSArtifact) getArtifact(masterUUID, context); return masterArtifact; } catch (ArtifactDatabaseException ade) { log.error(ade, ade); } return null; } /** * Return merged output document. * @param uuids List of artifact uuids. */ protected CollectionAttribute buildOutAttributes( ArtifactDatabase db, CallContext context, AttributeParser aParser, String[] uuids) { Document doc = XMLUtils.newDocument(); FLYSContext flysContext = FLYSUtils.getFlysContext(context); StateEngine engine = (StateEngine) flysContext.get( FLYSContext.STATE_ENGINE_KEY); FLYSArtifact masterArtifact = getMasterArtifact(context); XMLUtils.ElementCreator ec = new XMLUtils.ElementCreator( doc, ArtifactNamespaceContext.NAMESPACE_URI, ArtifactNamespaceContext.NAMESPACE_PREFIX); OutputParser oParser = new OutputParser(db, context); if (uuids != null) { for (String uuid: uuids) { try { oParser.parse(uuid); } catch (ArtifactDatabaseException ade) { log.warn(ade, ade); } } } aParser.parse(); return new AttributeWriter( db, aParser.getCollectionAttribute(), aParser.getOuts(), aParser.getFacets(), oParser.getOuts(), oParser.getFacets(), engine.getCompatibleFacets(masterArtifact.getStateHistoryIds()) ).write(); } /** * Returns the "attribute" (part of description document) for a specific * output type. * * @param context The CallContext object. * @param cAttr The CollectionAttribute. * @param output The name of the desired output type. * * @return the attribute for the desired output type. */ protected Document getAttribute( CallContext context, CollectionAttribute cAttr, String output) throws ArtifactDatabaseException { Document attr = cAttr.toXML(); Node out = (Node) XMLUtils.xpath( attr, "art:attribute/art:outputs/art:output[@name='" + output + "']", XPathConstants.NODE, ArtifactNamespaceContext.INSTANCE); if (out != null) { Document o = XMLUtils.newDocument(); o.appendChild(o.importNode(out, true)); return o; } return null; } /** * This method returns the list of artifact UUIDs that this collections * contains. * * @param context The CallContext that is necessary to get information about * the ArtifactDatabase. * * @return a list of uuids. */ protected String[] getArtifactUUIDs(CallContext context) throws ArtifactDatabaseException { log.debug("FLYSArtifactCollection.getArtifactUUIDs"); ArtifactDatabase db = context.getDatabase(); CallMeta meta = context.getMeta(); Document itemList = db.listCollectionArtifacts(identifier(), meta); NodeList items = (NodeList) XMLUtils.xpath( itemList, XPATH_COLLECTION_ITEMS, XPathConstants.NODESET, ArtifactNamespaceContext.INSTANCE); if (items == null || items.getLength() == 0) { log.debug("No artifacts found in this collection."); return null; } int num = items.getLength(); List<String> uuids = new ArrayList<String>(num); for (int i = 0; i < num; i++) { String uuid = XMLUtils.xpathString( items.item(i), "@art:uuid", ArtifactNamespaceContext.INSTANCE); if (uuid != null && uuid.trim().length() != 0) { uuids.add(uuid); } } return (String[]) uuids.toArray(new String[uuids.size()]); } /** * Returns a concrete Artifact of this collection specified by its uuid. * * @param uuid The Artifact's uuid. * @param context The CallContext. * * @return an Artifact. */ protected Artifact getArtifact(String uuid, CallContext context) throws ArtifactDatabaseException { log.debug("FLYSArtifactCollection.getArtifact"); Backend backend = Backend.getInstance(); PersistentArtifact persistent = backend.getArtifact(uuid); return persistent != null ? persistent.getArtifact() : null; } /** * Returns the attribute that belongs to an artifact and facet stored in * this collection. * * @param uuid The Artifact's uuid. * @param outname The name of the requested output. * @param facet The name of the requested facet. * @param context The CallContext. * * @return an attribute in form of a document. */ protected Document getFacetThemeFromAttribute( String uuid, String outName, String facet, String pattern, int index, CallContext context) throws ArtifactDatabaseException { log.debug("FLYSArtifactCollection.getFacetThemeFromAttribute"); ArtifactDatabase db = context.getDatabase(); CallMeta meta = context.getMeta(); FLYSContext flysContext = context instanceof FLYSContext ? (FLYSContext) context : (FLYSContext) context.globalContext(); Document attr = db.getCollectionItemAttribute(identifier(), uuid, meta); if (attr == null) { attr = initItemAttribute(uuid, facet, pattern, index, outName, context); if (attr == null) { return null; } } log.debug("Search attribute of collection item: " + uuid); Node tmp = (Node) XMLUtils.xpath( attr, "/art:attribute", XPathConstants.NODE, ArtifactNamespaceContext.INSTANCE); if (tmp == null) { log.warn("No attribute found. Operation failed."); return null; } log.debug("Search theme for facet '" + facet + "' in attribute."); Node theme = (Node) XMLUtils.xpath( tmp, "art:themes/theme[@facet='" + facet + "' and @index='" + String.valueOf(index) + "']", XPathConstants.NODE, ArtifactNamespaceContext.INSTANCE); if (theme == null) { log.warn("Could not find the theme in attribute of: " + uuid); Theme t = getThemeForFacet( uuid, facet, pattern, index, outName, context); if (t == null) { log.warn("No theme found for facet: " + facet); return null; } addThemeToAttribute(uuid, attr, t, context); theme = t.toXML().getFirstChild(); } Document doc = XMLUtils.newDocument(); doc.appendChild(doc.importNode(theme, true)); return doc; } /** * Adds the theme of a facet to a CollectionItem's attribute. * * @param uuid The uuid of the artifact. * @param attr The current attribute of an artifact. * @param t The theme to add. * @param context The CallContext. */ protected void addThemeToAttribute( String uuid, Document attr, Theme t, CallContext context) { log.debug("FLYSArtifactCollection.addThemeToAttribute: " + uuid); if (t == null) { log.warn("Theme is empty - cancel adding it to attribute!"); return; } XMLUtils.ElementCreator ec = new XMLUtils.ElementCreator( attr, ArtifactNamespaceContext.NAMESPACE_URI, ArtifactNamespaceContext.NAMESPACE_PREFIX); Node tmp = (Node) XMLUtils.xpath( attr, "/art:attribute", XPathConstants.NODE, ArtifactNamespaceContext.INSTANCE); if (tmp == null) { tmp = ec.create("attribute"); attr.appendChild(tmp); } Node themes = (Node) XMLUtils.xpath( tmp, "art:themes", XPathConstants.NODE, ArtifactNamespaceContext.INSTANCE); if (themes == null) { themes = ec.create("themes"); tmp.appendChild(themes); } themes.appendChild(attr.importNode(t.toXML().getFirstChild(), true)); try { setCollectionItemAttribute(uuid, attr, context); log.debug("Successfully added theme to item attribute."); } catch (ArtifactDatabaseException e) { // do nothing log.warn("Cannot set attribute of item: " + uuid); } } /** * Initializes the attribute of an collection item with the theme of a * specific facet. * * @param uuid The uuid of an artifact. * @param facet The name of a facet. * @param context The CallContext. * * @param the new attribute. */ protected Document initItemAttribute( String uuid, String facet, String pattern, int index, String outName, CallContext context) { log.info("FLYSArtifactCollection.initItemAttribute"); Theme t = getThemeForFacet(uuid, facet, pattern, index, outName, context); if (t == null) { log.info("Could not find theme for facet. Cancel initialization."); return null; } Document attr = XMLUtils.newDocument(); addThemeToAttribute(uuid, attr, t, context); return attr; } /** * Sets the attribute of a CollectionItem specified by <i>uuid</i> to a new * value <i>attr</i>. * * @param uuid The uuid of the CollectionItem. * @param attr The new attribute for the CollectionItem. * @param context The CallContext. */ public void setCollectionItemAttribute( String uuid, Document attr, CallContext context) throws ArtifactDatabaseException { Document doc = ClientProtocolUtils.newSetItemAttributeDocument( uuid, attr); if (doc == null) { log.warn("Cannot set item attribute: No attribute found."); return; } ArtifactDatabase db = context.getDatabase(); CallMeta meta = context.getMeta(); db.setCollectionItemAttribute(identifier(), uuid, doc, meta); } /** * Returns the theme of a specific facet. * * @param uuid The uuid of an artifact. * @param facet The name of the facet. * @param context The CallContext object. * * @return the desired theme. */ protected Theme getThemeForFacet( String uuid, String facet, String pattern, int index, String outName, CallContext context) { log.info("FLYSArtifactCollection.getThemeForFacet: " + facet); FLYSContext flysContext = context instanceof FLYSContext ? (FLYSContext) context : (FLYSContext) context.globalContext(); // Push artifact in flysContext. ArtifactDatabase db = context.getDatabase(); try { FLYSArtifact artifact = (FLYSArtifact) db.getRawArtifact(uuid); log.debug("Got raw artifact"); flysContext.put(flysContext.ARTIFACT_KEY, artifact); } catch (ArtifactDatabaseException dbe) { log.error("Exception caught when trying to get art.", dbe); } Theme t = ThemeFactory.getTheme(flysContext, facet, pattern, outName); if (t != null) { t.setFacet(facet); t.setIndex(index); } return t; } /** * Returns the OutGenerator for a specified <i>type</i>. * * @param name The name of the output type. * @param type Defines the type of the desired OutGenerator. * * @return Instance of an OutGenerator for specified <i>type</i>. */ protected OutGenerator getOutGenerator( CallContext context, String name, String type) { log.debug("Search OutGenerator for Output '" + name + "'"); FLYSContext flysContext = context instanceof FLYSContext ? (FLYSContext) context : (FLYSContext) context.globalContext(); Map<String, Class> generators = (Map<String, Class>) flysContext.get(FLYSContext.OUTGENERATORS_KEY); if (generators == null) { log.error("No output generators found in the running application!"); return null; } Class clazz = generators.get(name); try { return clazz != null ? (OutGenerator) clazz.newInstance() : null; } catch (InstantiationException ie) { log.error(ie, ie); } catch (IllegalAccessException iae) { log.error(iae, iae); } return null; } /** * Inner class to structure/order the themes of a chart. */ private static class ThemeList { private Logger logger = Logger.getLogger(ThemeList.class); protected Map<Integer, ManagedFacet> themes; public ThemeList(Document output) { themes = new HashMap<Integer, ManagedFacet>(); parse(output); } protected void parse(Document output) { NodeList themeList = (NodeList) XMLUtils.xpath( output, "art:output/art:facet", XPathConstants.NODESET, ArtifactNamespaceContext.INSTANCE); int num = themeList != null ? themeList.getLength() : 0; logger.debug("Output has " + num + " elements."); if (num == 0) { return; } String uri = ArtifactNamespaceContext.NAMESPACE_URI; for (int i = 0; i < num; i++) { Element theme = (Element) themeList.item(i); ManagedDomFacet facet = new ManagedDomFacet(theme); themes.put(Integer.valueOf(facet.getPosition()-1), facet); } } public ManagedFacet get(int idx) { return themes.get(Integer.valueOf(idx)); } public int size() { return themes.size(); } } } // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :